From d641b5b9e051f0e9d5178e076c7f745c60bcbe37 Mon Sep 17 00:00:00 2001 From: dennismeister93 Date: Fri, 13 Oct 2023 15:19:04 +0200 Subject: [PATCH] fix: create command adaptions (#168) --- src/commands/cache/clear.ts | 3 +- src/commands/cache/get.ts | 2 +- src/commands/cache/set.ts | 2 +- src/commands/create/index.ts | 69 +++++----- src/commands/exec/index.ts | 2 +- src/commands/init/index.ts | 4 +- src/commands/package/index.ts | 2 +- src/commands/sync/index.ts | 2 +- src/commands/upgrade/index.ts | 6 +- src/modules/project-config.ts | 13 +- test/commands/create/create.test.ts | 118 +++++++++++++----- test/unit/project-config.test.ts | 4 +- test/unit/variables.test.ts | 2 +- .../vehicle-app-template/package-index.json | 7 +- 14 files changed, 143 insertions(+), 93 deletions(-) diff --git a/src/commands/cache/clear.ts b/src/commands/cache/clear.ts index 75b97ad0..a99cf9d1 100644 --- a/src/commands/cache/clear.ts +++ b/src/commands/cache/clear.ts @@ -26,8 +26,7 @@ export default class Clear extends Command { // although we are not reading the project config, we want to // ensure the command is run in a project directory only. - ProjectConfig.read(); - + ProjectConfig.read(`v${this.config.version}`); const cache = ProjectCache.read(); cache.clear(); cache.write(); diff --git a/src/commands/cache/get.ts b/src/commands/cache/get.ts index 9a144144..fe599b89 100644 --- a/src/commands/cache/get.ts +++ b/src/commands/cache/get.ts @@ -36,7 +36,7 @@ bar`, // although we are not reading the project config, we want to // ensure the command is run in a project directory only. - ProjectConfig.read(); + ProjectConfig.read(`v${this.config.version}`); const cache = ProjectCache.read(); diff --git a/src/commands/cache/set.ts b/src/commands/cache/set.ts index 62bc1d39..18f55d2a 100644 --- a/src/commands/cache/set.ts +++ b/src/commands/cache/set.ts @@ -31,7 +31,7 @@ export default class Set extends Command { // although we are not reading the project config, we want to // ensure the command is run in a project directory only. - ProjectConfig.read(); + ProjectConfig.read(`v${this.config.version}`); const cache = ProjectCache.read(); cache.set(args.key, args.value); diff --git a/src/commands/create/index.ts b/src/commands/create/index.ts index 3da80496..2ea62a14 100644 --- a/src/commands/create/index.ts +++ b/src/commands/create/index.ts @@ -101,6 +101,15 @@ export default class Create extends Command { name: arg.id, prefix: '', message: `Config '${arg.id}' for interface '${interfaceEntry}': ${arg.description}`, + default: arg.default, + validate: (input: any) => { + if (!input) { + console.log('No empty value allowed for required argument!'); + return false; + } else { + return true; + } + }, type: 'input', }; }, @@ -117,27 +126,23 @@ export default class Create extends Command { } private async _runInteractiveMode(flags: any) { - let interactiveResponses: any = await inquirer.prompt([ - Create.prompts.name, - Create.prompts.language, - Create.prompts.exampleQuestion, - ]); + const interactiveResponses: any = await inquirer.prompt([Create.prompts.language, Create.prompts.exampleQuestion]); - flags.name = interactiveResponses.name; flags.language = interactiveResponses.language; flags.example = interactiveResponses.exampleQuestion; if (flags.example) { availableExamples = this._filterAvailableExamplesByLanguage(flags.language); - interactiveResponses = await inquirer.prompt([Create.prompts.exampleUse]); + const exampleResponse = await inquirer.prompt([Create.prompts.exampleUse]); + flags.example = flags.name = exampleResponse.exampleUse; } else { - interactiveResponses = await inquirer.prompt([Create.prompts.interface]); - } - flags.example = interactiveResponses.exampleUse; - flags.interface = interactiveResponses.interface; + const interactiveSkeletonAppResponses: any = await inquirer.prompt([Create.prompts.name, Create.prompts.interface]); + flags.name = interactiveSkeletonAppResponses.name; + flags.interface = interactiveSkeletonAppResponses.interface; - if (flags.interface && flags.interface.length > 0) { - await this._handleAdditionalInterfaceArgs(flags.interface); + if (flags.interface && flags.interface.length > 0) { + await this._handleAdditionalInterfaceArgs(flags.interface); + } } } @@ -185,30 +190,6 @@ export default class Create extends Command { }); } - private async _setDefaultAppManifestInterfaceConfig(interfaces: string[]) { - if (this.appManifestInterfaces.interfaces.length > 0) { - return; - } - const interfacesToUse = - Array.isArray(interfaces) && interfaces.length - ? availableInterfaces.filter((interfaceEntry) => interfaces.includes(interfaceEntry.value)) - : availableInterfaces; - - for (const interfaceEntry of interfacesToUse) { - const defaultAppManifestInterfaceConfig: AppManifestInterfaceEntry = { - type: interfaceEntry.value, - config: {}, - }; - - for (const arg of interfaceEntry.args) { - defaultAppManifestInterfaceConfig.config[arg.id] = - arg.type === 'object' && arg.default ? JSON.parse(arg.default) : arg.default; - } - - this.appManifestInterfaces.interfaces.push(defaultAppManifestInterfaceConfig); - } - } - private _getScriptExecutionPath(sdkConfig: SdkConfig): string { const basePath = process.env.VELOCITAS_SDK_PATH_OVERRIDE ? process.env.VELOCITAS_SDK_PATH_OVERRIDE @@ -223,6 +204,14 @@ export default class Create extends Command { const { flags } = await this.parse(Create); this.log(`Creating a new Velocitas project ...`); + if (flags.name && flags.example) { + throw new Error("Flags 'name' and 'example' are mutually exclusive!"); + } + + if (flags.example) { + flags.name = flags.example; + } + if (Object.keys(flags).length === 0) { this.log('Interactive project creation started'); await this._runInteractiveMode(flags); @@ -235,8 +224,10 @@ export default class Create extends Command { throw new Error("Missing required flag 'language'"); } - if (!flags.example) { - this._setDefaultAppManifestInterfaceConfig(flags.interface!); + if (!flags.example && flags.interface) { + if (this.appManifestInterfaces.interfaces.length === 0 && flags.interface.length > 0) { + await this._handleAdditionalInterfaceArgs(flags.interface); + } } await ProjectConfig.create(packageIndex.getExtensions(), flags.language, this.config.version); diff --git a/src/commands/exec/index.ts b/src/commands/exec/index.ts index 7a94e465..7a193426 100644 --- a/src/commands/exec/index.ts +++ b/src/commands/exec/index.ts @@ -76,7 +76,7 @@ Executing script... const programArgsAndFlags = this._extractProgramArgsAndFlags(); const { args, flags } = await this.parse(Exec); - const projectConfig = ProjectConfig.read(); + const projectConfig = ProjectConfig.read(`v${this.config.version}`); const execSpec: ExecSpec = { ref: args.ref, diff --git a/src/commands/init/index.ts b/src/commands/init/index.ts index 32e4936f..7483ac5a 100644 --- a/src/commands/init/index.ts +++ b/src/commands/init/index.ts @@ -113,10 +113,10 @@ Velocitas project found! if (!ProjectConfig.isAvailable()) { this.log('... Directory is no velocitas project. Creating .velocitas.json at the root of your repository.'); - projectConfig = new ProjectConfig(); + projectConfig = new ProjectConfig(`v${this.config.version}`); projectConfig.write(); } - projectConfig = ProjectConfig.read(); + projectConfig = ProjectConfig.read(`v${this.config.version}`); for (const packageConfig of projectConfig.packages) { if (!flags.force && packageConfig.isPackageInstalled()) { diff --git a/src/commands/package/index.ts b/src/commands/package/index.ts index bf91b76b..91078349 100644 --- a/src/commands/package/index.ts +++ b/src/commands/package/index.ts @@ -49,7 +49,7 @@ $ velocitas component --get-path devenv-runtime-local async run(): Promise { const { args, flags } = await this.parse(Package); - const projectConfig = ProjectConfig.read(); + const projectConfig = ProjectConfig.read(`v${this.config.version}`); let packagesToPrint: Array; diff --git a/src/commands/sync/index.ts b/src/commands/sync/index.ts index f8e521df..b9241aa7 100644 --- a/src/commands/sync/index.ts +++ b/src/commands/sync/index.ts @@ -30,7 +30,7 @@ Syncing Velocitas components! async run(): Promise { this.log(`Syncing Velocitas components!`); - const projectConfig = ProjectConfig.read(); + const projectConfig = ProjectConfig.read(`v${this.config.version}`); const setupComponents = findComponentsByType(projectConfig, ComponentType.setup); for (const setupComponent of setupComponents) { this.log(`... syncing '${setupComponent[0].getPackageName()}'`); diff --git a/src/commands/upgrade/index.ts b/src/commands/upgrade/index.ts index d0c5c54d..f5aa4d07 100644 --- a/src/commands/upgrade/index.ts +++ b/src/commands/upgrade/index.ts @@ -37,7 +37,7 @@ Checking for updates! const { flags } = await this.parse(Upgrade); this.log(`Checking for updates!`); - const projectConfig = ProjectConfig.read(); + const projectConfig = ProjectConfig.read(`v${this.config.version}`); for (const packageConfig of projectConfig.packages) { const availableVersions = await packageConfig.getPackageVersions(); try { @@ -75,5 +75,9 @@ Checking for updates! this.error(`Error during upgrade: '${e}'`); } } + if (!projectConfig.cliVersion) { + projectConfig.cliVersion = `v${this.config.version}`; + projectConfig.write(); + } } } diff --git a/src/modules/project-config.ts b/src/modules/project-config.ts index e0399734..9f169082 100644 --- a/src/modules/project-config.ts +++ b/src/modules/project-config.ts @@ -27,6 +27,7 @@ export const DEFAULT_CONFIG_FILE_PATH = resolve(cwd(), './.velocitas.json'); interface ProjectConfigOptions { packages: PackageConfig[]; variables: Map; + cliVersion?: string; } export class ProjectConfig implements ProjectConfigOptions { @@ -35,7 +36,7 @@ export class ProjectConfig implements ProjectConfigOptions { // project-wide variable configuration variables: Map = new Map(); - cliVersion: string | undefined; + cliVersion: string; private static _parsePackageConfig(packages: PackageConfig[]): PackageConfig[] { const configArray: PackageConfig[] = []; @@ -45,16 +46,17 @@ export class ProjectConfig implements ProjectConfigOptions { return configArray; } - constructor(config?: ProjectConfigOptions) { + constructor(cliVersion: string, config?: ProjectConfigOptions) { this.packages = config?.packages ? ProjectConfig._parsePackageConfig(config.packages) : this.packages; this.variables = config?.variables ? config.variables : this.variables; + this.cliVersion = config?.cliVersion ? config.cliVersion : cliVersion; } - static read(path: PathLike = DEFAULT_CONFIG_FILE_PATH): ProjectConfig { + static read(cliVersion: string, path: PathLike = DEFAULT_CONFIG_FILE_PATH): ProjectConfig { let config: ProjectConfig; try { - config = new ProjectConfig(JSON.parse(readFileSync(path, DEFAULT_BUFFER_ENCODING))); + config = new ProjectConfig(cliVersion, JSON.parse(readFileSync(path, DEFAULT_BUFFER_ENCODING))); } catch (error) { throw new Error(`Error in parsing .velocitas.json: ${(error as Error).message}`); } @@ -83,7 +85,7 @@ export class ProjectConfig implements ProjectConfigOptions { static isAvailable = (path: PathLike = DEFAULT_CONFIG_FILE_PATH) => existsSync(path); static async create(usedExtensions: PkgIndexEntry[], language: string, cliVersion: string) { - const projectConfig = new ProjectConfig(); + const projectConfig = new ProjectConfig(`v${cliVersion}`); for (const extension of usedExtensions) { const packageConfig = new PackageConfig({ name: extension.package }); const versions = await packageConfig.getPackageVersions(); @@ -97,7 +99,6 @@ export class ProjectConfig implements ProjectConfigOptions { projectConfig.variables.set('repoType', 'app'); projectConfig.variables.set('appManifestPath', DEFAULT_APP_MANIFEST_PATH); projectConfig.variables.set('githubRepoId', ''); - projectConfig.cliVersion = cliVersion; projectConfig.write(); } diff --git a/test/commands/create/create.test.ts b/test/commands/create/create.test.ts index b4acfbeb..712bff4c 100644 --- a/test/commands/create/create.test.ts +++ b/test/commands/create/create.test.ts @@ -58,22 +58,23 @@ describe('create', () => { .stdout() .stub(gitModule, 'simpleGit', sinon.stub().returns(simpleGitInstanceMock())) .stub(exec, 'runExecSpec', () => {}) - .stub(exec, 'awaitSpawn', () => {}) + .stub(exec, 'awaitSpawn', () => { + return { exitCode: 0 }; + }) .command(['create', '-n', TEST_APP_NAME, '-l', 'test']) - .it('creates project with provided flags and generates .velocitas.json and AppManifest', (ctx) => { + .it('creates a project with provided flags and generates .velocitas.json and AppManifest', (ctx) => { expect(ctx.stdout).to.equal(EXPECTED_NON_INTERACTIVE_STDOUT); expect(ProjectConfig.isAvailable()).to.be.true; expect(readAppManifest()).to.not.be.undefined; - const velocitasConfig = ProjectConfig.read(); + const velocitasConfig = ProjectConfig.read('v0.0.0'); expect(velocitasConfig.packages[0].repo).to.be.equal(TEST_PACKAGE_URI); expect(velocitasConfig.packages[0].version).to.be.equal(TEST_PACKAGE_VERSION); expect(velocitasConfig.variables.get('language')).to.be.equal('test'); const appManifest = readAppManifest(); expect(appManifest.name).to.be.equal(TEST_APP_NAME); - expect(appManifest.interfaces[0].type).to.be.equal(TEST_EXPOSED_INTERFACE_TYPE); - expect(appManifest.interfaces[0].config[TEST_EXPOSED_INTERFACE_ARG_NAME_1]).to.be.equal(TEST_EXPOSED_INTERFACE_ARG_DEFAULT_1); + expect(appManifest.interfaces).to.be.empty; }); test.do(() => { @@ -87,7 +88,6 @@ describe('create', () => { .stub(exec, 'runExecSpec', () => {}) .command(['create', '-n', TEST_APP_NAME, '-l', 'test']) .catch('Unable to execute create script!') - .only() .it('throws error when project-creation script cannot be executed'); test.do(() => { @@ -132,7 +132,9 @@ describe('create', () => { .stdout() .stub(gitModule, 'simpleGit', sinon.stub().returns(simpleGitInstanceMock())) .stub(exec, 'runExecSpec', () => {}) - .stub(exec, 'awaitSpawn', () => {}) + .stub(exec, 'awaitSpawn', () => { + return { exitCode: 0 }; + }) .stub(inquirer, 'prompt', () => { return { name: TEST_APP_NAME, @@ -142,12 +144,12 @@ describe('create', () => { }; }) .command(['create']) - .it('creates project in interactive mode without example and generates .velocitas.json and AppManifest with defaults', (ctx) => { + .it('creates a project in interactive mode without example and generates .velocitas.json and AppManifest with defaults', (ctx) => { expect(ctx.stdout).to.equal(EXPECTED_INTERACTIVE_STDOUT); expect(ProjectConfig.isAvailable()).to.be.true; expect(readAppManifest()).to.not.be.undefined; - const velocitasConfig = ProjectConfig.read(); + const velocitasConfig = ProjectConfig.read('v0.0.0'); expect(velocitasConfig.packages[0].repo).to.be.equal(TEST_PACKAGE_URI); expect(velocitasConfig.packages[0].version).to.be.equal(TEST_PACKAGE_VERSION); expect(velocitasConfig.variables.get('language')).to.be.equal('test'); @@ -161,6 +163,45 @@ describe('create', () => { ); }); + test.do(() => { + mockFolders({ packageIndex: true }); + }) + .finally(() => { + mockRestore(); + }) + .stdout() + .stub(gitModule, 'simpleGit', sinon.stub().returns(simpleGitInstanceMock())) + .stub(exec, 'runExecSpec', () => {}) + .stub(exec, 'awaitSpawn', () => { + return { exitCode: 0 }; + }) + .stub(inquirer, 'prompt', () => { + return { + name: TEST_APP_NAME, + language: 'test', + exampleQuestion: false, + interface: [], + }; + }) + .command(['create']) + .it( + 'creates a project in interactive mode with either example or interfaces and generates .velocitas.json and AppManifest correctly', + (ctx) => { + expect(ctx.stdout).to.equal(EXPECTED_INTERACTIVE_STDOUT); + expect(ProjectConfig.isAvailable()).to.be.true; + expect(readAppManifest()).to.not.be.undefined; + + const velocitasConfig = ProjectConfig.read('v0.0.0'); + expect(velocitasConfig.packages[0].repo).to.be.equal(TEST_PACKAGE_URI); + expect(velocitasConfig.packages[0].version).to.be.equal(TEST_PACKAGE_VERSION); + expect(velocitasConfig.variables.get('language')).to.be.equal('test'); + + const appManifest = readAppManifest(); + expect(appManifest.name).to.be.equal(TEST_APP_NAME); + expect(appManifest.interfaces).to.be.empty; + }, + ); + test.do(() => { mockFolders({ packageIndex: true }); }) @@ -181,7 +222,9 @@ describe('create', () => { .stdout() .stub(gitModule, 'simpleGit', sinon.stub().returns(simpleGitInstanceMock())) .stub(exec, 'runExecSpec', () => {}) - .stub(exec, 'awaitSpawn', () => {}) + .stub(exec, 'awaitSpawn', () => { + return { exitCode: 0 }; + }) .stub(inquirer, 'prompt', () => { return { name: TEST_APP_NAME, @@ -193,6 +236,17 @@ describe('create', () => { .catch(`No example for your chosen language 'no-example' available`) .it('throws error when no example exists for chosen language'); + test.do(() => { + mockFolders({ packageIndex: true }); + }) + .finally(() => { + mockRestore(); + }) + .stdout() + .command(['create', '-n', 'test', '-e', 'example']) + .catch(`Flags 'name' and 'example' are mutually exclusive!`) + .it('throws error when name and example flags are used in parallel'); + test.do(() => { mockFolders({ packageIndex: true }); }) @@ -202,7 +256,9 @@ describe('create', () => { .stdout() .stub(gitModule, 'simpleGit', sinon.stub().returns(simpleGitInstanceMock())) .stub(exec, 'runExecSpec', () => {}) - .stub(exec, 'awaitSpawn', () => {}) + .stub(exec, 'awaitSpawn', () => { + return { exitCode: 0 }; + }) .stub(inquirer, 'prompt', () => { return { name: TEST_APP_NAME, @@ -213,21 +269,24 @@ describe('create', () => { }; }) .command(['create']) - .it('creates project in interactive mode without example and generates .velocitas.json and AppManifest without defaults', (ctx) => { - expect(ctx.stdout).to.equal(EXPECTED_INTERACTIVE_STDOUT); - expect(ProjectConfig.isAvailable()).to.be.true; - expect(readAppManifest()).to.not.be.undefined; + .it( + 'creates a project in interactive mode without example and generates .velocitas.json and AppManifest without defaults', + (ctx) => { + expect(ctx.stdout).to.equal(EXPECTED_INTERACTIVE_STDOUT); + expect(ProjectConfig.isAvailable()).to.be.true; + expect(readAppManifest()).to.not.be.undefined; - const velocitasConfig = ProjectConfig.read(); - expect(velocitasConfig.packages[0].repo).to.be.equal(TEST_PACKAGE_URI); - expect(velocitasConfig.packages[0].version).to.be.equal(TEST_PACKAGE_VERSION); - expect(velocitasConfig.variables.get('language')).to.be.equal('test'); + const velocitasConfig = ProjectConfig.read('v0.0.0'); + expect(velocitasConfig.packages[0].repo).to.be.equal(TEST_PACKAGE_URI); + expect(velocitasConfig.packages[0].version).to.be.equal(TEST_PACKAGE_VERSION); + expect(velocitasConfig.variables.get('language')).to.be.equal('test'); - const appManifest = readAppManifest(); - expect(appManifest.name).to.be.equal(TEST_APP_NAME); - expect(appManifest.interfaces[0].type).to.be.equal(TEST_EXPOSED_INTERFACE_TYPE); - expect(appManifest.interfaces[0].config[TEST_EXPOSED_INTERFACE_ARG_NAME_1]).to.be.equal('testNotDefault'); - }); + const appManifest = readAppManifest(); + expect(appManifest.name).to.be.equal(TEST_APP_NAME); + expect(appManifest.interfaces[0].type).to.be.equal(TEST_EXPOSED_INTERFACE_TYPE); + expect(appManifest.interfaces[0].config[TEST_EXPOSED_INTERFACE_ARG_NAME_1]).to.be.equal('testNotDefault'); + }, + ); test.do(() => { mockFolders({ packageIndex: true }); @@ -238,22 +297,23 @@ describe('create', () => { .stdout() .stub(gitModule, 'simpleGit', sinon.stub().returns(simpleGitInstanceMock())) .stub(exec, 'runExecSpec', () => {}) - .stub(exec, 'awaitSpawn', () => {}) + .stub(exec, 'awaitSpawn', () => { + return { exitCode: 0 }; + }) .stub(inquirer, 'prompt', () => { return { - name: TEST_APP_NAME, language: 'test', exampleQuestion: true, - exampleUse: true, + exampleUse: TEST_APP_NAME, }; }) .command(['create']) - .it('creates project in interactive mode with example and generates .velocitas.json and AppManifest', (ctx) => { + .it('creates a project in interactive mode with example and generates .velocitas.json and AppManifest', (ctx) => { expect(ctx.stdout).to.equal(EXPECTED_INTERACTIVE_STDOUT); expect(ProjectConfig.isAvailable()).to.be.true; expect(readAppManifest()).to.not.be.undefined; - const velocitasConfig = ProjectConfig.read(); + const velocitasConfig = ProjectConfig.read('v0.0.0'); expect(velocitasConfig.packages[0].repo).to.be.equal(TEST_PACKAGE_URI); expect(velocitasConfig.packages[0].version).to.be.equal(TEST_PACKAGE_VERSION); expect(velocitasConfig.variables.get('language')).to.be.equal('test'); diff --git a/test/unit/project-config.test.ts b/test/unit/project-config.test.ts index 0ce2a763..3cf9d6b4 100644 --- a/test/unit/project-config.test.ts +++ b/test/unit/project-config.test.ts @@ -35,10 +35,10 @@ describe('project-config - module', () => { }); describe('.velocitas.json parsing', () => { it('should throw an error when .velocitas.json is invalid.', () => { - expect(ProjectConfig.read.bind(ProjectConfig.read, '/.velocitasInvalid.json')).to.throw(); + expect(ProjectConfig.read.bind(ProjectConfig.read, ...['v0.0.0', '/.velocitasInvalid.json'])).to.throw(); }); it('should read the ProjectConfig when .velocitas.json is valid.', () => { - expect(ProjectConfig.read.bind(ProjectConfig.read, '/.velocitasValid.json')).to.not.throw(); + expect(ProjectConfig.read.bind(ProjectConfig.read, ...['v0.0.0', '/.velocitasValid.json'])).to.not.throw(); }); }); after(() => { diff --git a/test/unit/variables.test.ts b/test/unit/variables.test.ts index b4dd8b1e..d4038592 100644 --- a/test/unit/variables.test.ts +++ b/test/unit/variables.test.ts @@ -31,7 +31,7 @@ describe('variables - module', () => { variablesObject = { testString: 'test', testNumber: 1 }; variablesMap = new Map(Object.entries(variablesObject)); packageConfig = new PackageConfig({ name: 'test-package', version: 'v1.1.1', variables: variablesMap, components: [] }); - projectConfig = new ProjectConfig({ packages: [packageConfig], variables: variablesMap }); + projectConfig = new ProjectConfig('v0.0.0', { packages: [packageConfig], variables: variablesMap }); componentConfig = { id: 'test-component', variables: variablesMap }; componentManifest = { diff --git a/testbench/test-create/vehicle-app-template/package-index.json b/testbench/test-create/vehicle-app-template/package-index.json index 223f4019..d26d6af9 100644 --- a/testbench/test-create/vehicle-app-template/package-index.json +++ b/testbench/test-create/vehicle-app-template/package-index.json @@ -9,7 +9,7 @@ "args": [ { "id": "src", - "description": "URI or path to VSS json (Leave empty for default: v3.0)", + "description": "URI or path to VSS json", "default": "https://github.com/COVESA/vehicle_signal_specification/releases/download/v3.0/vss_rel_3.0.json", "required": true, "type": "string" @@ -79,11 +79,6 @@ "id": "seat-adjuster", "description": "Seat Adjuster Example", "type": "string" - }, - { - "id": "dog-mode", - "description": "Dog Mode Example", - "type": "string" } ] }