diff --git a/packages/cli/src/lib/setup.ts b/packages/cli/src/lib/setup.ts index f59752e55f..cbd4bde2e3 100644 --- a/packages/cli/src/lib/setup.ts +++ b/packages/cli/src/lib/setup.ts @@ -20,7 +20,14 @@ import { CLIMessage } from '@/lib/cli/cliMessage.js'; import { CLIPlugin } from '@/lib/cli/cliPlugin.js'; import { error as CLIError } from '@/lib/cli/messages.js'; import type { CLICommandContext, CLICommandOptions } from '@/lib/cli/cliCommand.js'; -import { getRepository, ignoreVersion, recognizeVersion } from '@/lib/setup/utils.js'; +import { + BETA_REPO_URL, + getRepository, + ignoreVersion, + isIntegerLikeInput, + recognizeVersion, + STABLE_REPO_URL, +} from '@/lib/setup/utils.js'; import { dbConnect, dbConnectAsync, exitApplicationSave } from '@/lib/setup/dbConnection.js'; import { IoBrokerError } from '@/lib/setup/customError.js'; import type { ListType } from '@/lib/setup/setupList.js'; @@ -30,6 +37,7 @@ import * as events from 'node:events'; // eslint-disable-next-line unicorn/prefer-module const thisDir = url.fileURLToPath(new URL('.', import.meta.url || `file://${__filename}`)); import { createRequire } from 'node:module'; +import { SYSTEM_CONFIG_ID, SYSTEM_REPOSITORIES_ID } from '@iobroker/js-controller-common-db/constants'; // eslint-disable-next-line unicorn/prefer-module const require = createRequire(import.meta.url || `file://${__filename}`); @@ -395,10 +403,10 @@ function initYargs(): ReturnType { .command('all', 'Show entire config') .command('[.]', 'Status of a specified adapter instance'); }) - .command('repo []', 'Show repo information', yargs => { + .command('repo', 'Show repo information', yargs => { yargs - .command('set ', 'Set active repository') - .command('del ', 'Remove repository') + .command('set |', 'Set active repository') + .command('del |', 'Remove repository') .command('add ', 'Add repository') .command('addset ', 'Add repository and set it as active one') .command('show', 'List repositories'); @@ -670,8 +678,8 @@ async function processCommand( const repo = new Repo({ objects, states }); try { - await repo.rename('default', 'stable', 'http://download.iobroker.net/sources-dist.json'); - await repo.rename('latest', 'beta', 'http://download.iobroker.net/sources-dist-latest.json'); + await repo.rename('default', 'stable', STABLE_REPO_URL); + await repo.rename('latest', 'beta', BETA_REPO_URL); } catch (err) { console.warn(`Cannot rename: ${err.message}`); } @@ -2514,9 +2522,9 @@ async function processCommand( } case 'repo': { - let repoUrlOrCommand = args[0]; // Repo url or name or "add" / "del" / "set" / "show" / "addset" / "unset" - const repoName = args[1]; // Repo url or name - let repoUrl = args[2]; // Repo url or name + let repoUrlOrCommand: string | undefined = args[0]; // Repo url or name or "add" / "del" / "set" / "show" / "addset" / "unset" + let repoName: string | undefined = args[1]; // Repo url or name + let repoUrl: string | undefined = args[2]; // Repo url or name if ( repoUrlOrCommand !== 'add' && repoUrlOrCommand !== 'del' && @@ -2529,91 +2537,102 @@ async function processCommand( repoUrlOrCommand = 'show'; } - dbConnect(params, async ({ objects, states }) => { - const { Repo } = await import('./setup/setupRepo.js'); - const repo = new Repo({ - objects, - states, - }); + const { objects, states } = await dbConnectAsync(false, params); + const { Repo } = await import('./setup/setupRepo.js'); + const repo = new Repo({ + objects, + states, + }); - if (repoUrlOrCommand === 'show') { + if (repoUrlOrCommand === 'show') { + try { + await repo.showRepoStatus(); + return void callback(); + } catch (e) { + console.error(`Cannot show repository status: ${e.message}`); + return void callback(EXIT_CODES.INVALID_REPO); + } + } + + if ( + repoUrlOrCommand === 'add' || + repoUrlOrCommand === 'del' || + repoUrlOrCommand === 'set' || + repoUrlOrCommand === 'addset' || + repoUrlOrCommand === 'unset' + ) { + if (!repoName || !repoName.match(/[-_\w]+/)) { + console.error(`Invalid repository name: "${repoName}"`); + return void callback(EXIT_CODES.INVALID_ARGUMENTS); + } + + if (isIntegerLikeInput(repoName)) { try { - await repo.showRepoStatus(); - return void callback(); - } catch (err) { - console.error(`Cannot show repository status: ${err.message}`); - return void callback(EXIT_CODES.INVALID_REPO); + repoName = await repo.getNameByIndex(parseInt(repoName)); + } catch (e) { + console.error(e.message); + return void callback(EXIT_CODES.INVALID_ARGUMENTS); } - } else if ( - repoUrlOrCommand === 'add' || - repoUrlOrCommand === 'del' || - repoUrlOrCommand === 'set' || - repoUrlOrCommand === 'addset' || - repoUrlOrCommand === 'unset' - ) { - if (!repoName || !repoName.match(/[-_\w\d]+/)) { - console.error(`Invalid repository name: "${repoName}"`); - return void callback(); + } + + if (repoUrlOrCommand === 'add' || repoUrlOrCommand === 'addset') { + if (!repoUrl) { + console.warn( + `Please define repository URL or path: ${tools.appName.toLowerCase()} add `, + ); + return void callback(EXIT_CODES.INVALID_ARGUMENTS); } - if (repoUrlOrCommand === 'add' || repoUrlOrCommand === 'addset') { - if (!repoUrl) { - console.warn( - `Please define repository URL or path: ${tools.appName.toLowerCase()} add `, - ); - return void callback(EXIT_CODES.INVALID_ARGUMENTS); - } - try { - await repo.add(repoName, repoUrl); + try { + await repo.add(repoName, repoUrl); - if (repoUrlOrCommand === 'addset') { - await repo.setActive(repoName); - console.log(`Repository "${repoName}" set as active: "${repoUrl}"`); - await repo.showRepoStatus(); - return void callback(); - } - console.log(`Repository "${repoName}" added as "${repoUrl}"`); - await repo.showRepoStatus(); - return void callback(); - } catch (err) { - console.error(`Cannot add repository location: ${err.message}`); - return void callback(EXIT_CODES.INVALID_REPO); - } - } else if (repoUrlOrCommand === 'set') { - try { + if (repoUrlOrCommand === 'addset') { await repo.setActive(repoName); - console.log(`Repository "${repoName}" set as active.`); + console.log(`Repository "${repoName}" set as active: "${repoUrl}"`); await repo.showRepoStatus(); return void callback(); - } catch (err) { - console.error(`Cannot activate repository: ${err.message}`); - return void callback(EXIT_CODES.INVALID_REPO); } - } else if (repoUrlOrCommand === 'del') { - try { - await repo.del(repoName); - console.log(`Repository "${repoName}" deleted.`); - await repo.showRepoStatus(); - return void callback(); - } catch (err) { - console.error(`Cannot remove repository: ${err.message}`); - return void callback(EXIT_CODES.INVALID_REPO); - } - } else if (repoUrlOrCommand === 'unset') { - try { - await repo.setInactive(repoName); - console.log(`Repository "${repoName}" deactivated.`); - await repo.showRepoStatus(); - return void callback(); - } catch (err) { - console.error(`Cannot deactivate repository: ${err.message}`); - return void callback(EXIT_CODES.INVALID_REPO); - } - } else { - console.warn(`Unknown repo command: ${repoUrlOrCommand as string}`); - return void callback(EXIT_CODES.INVALID_ARGUMENTS); + console.log(`Repository "${repoName}" added as "${repoUrl}"`); + await repo.showRepoStatus(); + return void callback(); + } catch (e) { + console.error(`Cannot add repository location: ${e.message}`); + return void callback(EXIT_CODES.INVALID_REPO); + } + } else if (repoUrlOrCommand === 'set') { + try { + await repo.setActive(repoName); + console.log(`Repository "${repoName}" set as active.`); + await repo.showRepoStatus(); + return void callback(); + } catch (e) { + console.error(`Cannot activate repository: ${e.message}`); + return void callback(EXIT_CODES.INVALID_REPO); + } + } else if (repoUrlOrCommand === 'del') { + try { + await repo.del(repoName); + console.log(`Repository "${repoName}" deleted.`); + await repo.showRepoStatus(); + return void callback(); + } catch (e) { + console.error(`Cannot remove repository: ${e.message}`); + return void callback(EXIT_CODES.INVALID_REPO); + } + } else if (repoUrlOrCommand === 'unset') { + try { + await repo.setInactive(repoName); + console.log(`Repository "${repoName}" deactivated.`); + await repo.showRepoStatus(); + return void callback(); + } catch (e) { + console.error(`Cannot deactivate repository: ${e.message}`); + return void callback(EXIT_CODES.INVALID_REPO); } + } else { + console.warn(`Unknown repo command: ${repoUrlOrCommand as string}`); + return void callback(EXIT_CODES.INVALID_ARGUMENTS); } - }); + } break; } @@ -2788,10 +2807,10 @@ const OBJECTS_THAT_CANNOT_BE_DELETED = [ 'alias.0', 'enum.functions', 'enum.rooms', - 'system.config', + SYSTEM_CONFIG_ID, 'system.group.administrator', 'system.group.user', - 'system.repositories', + SYSTEM_REPOSITORIES_ID, 'system.user.admin', ]; @@ -2869,40 +2888,40 @@ async function cleanDatabase(isDeleteDb: boolean): Promise { return keysCount; } -function unsetup(params: Record, callback: ExitCodeCb): void { - dbConnect(params, ({ objects }) => { - objects.delObject('system.meta.uuid', err => { - if (err) { - console.log(`uuid cannot be deleted: ${err.message}`); - } else { - console.log('system.meta.uuid deleted'); - } - objects.getObject('system.config', (_err, obj) => { - if (obj?.common.licenseConfirmed || obj?.common.language || obj?.native?.secret) { - obj.common.language = 'en'; - // allow with parameter --keepsecret to not delete the secret - // This is very specific use case for vendors and must not be described in documentation - if (!params.keepsecret) { - obj.common.licenseConfirmed = false; - obj.native && delete obj.native.secret; - } +async function unsetup(params: Record, callback: ExitCodeCb): Promise { + const { objects } = await dbConnectAsync(false, params); - obj.from = `system.host.${tools.getHostName()}.cli`; - obj.ts = new Date().getTime(); + objects.delObject('system.meta.uuid', err => { + if (err) { + console.log(`uuid cannot be deleted: ${err.message}`); + } else { + console.log('system.meta.uuid deleted'); + } + objects.getObject(SYSTEM_CONFIG_ID, (_err, obj) => { + if (obj?.common.licenseConfirmed || obj?.common.language || obj?.native?.secret) { + obj.common.language = 'en'; + // allow with parameter --keepsecret to not delete the secret + // This is very specific use case for vendors and must not be described in documentation + if (!params.keepsecret) { + obj.common.licenseConfirmed = false; + obj.native && delete obj.native.secret; + } - objects.setObject('system.config', obj as any, err => { - if (err) { - console.log(`not found: ${err.message}`); - return void callback(EXIT_CODES.CANNOT_SET_OBJECT); - } - console.log('system.config reset'); - return void callback(); - }); - } else { - console.log('system.config is OK'); + obj.from = `system.host.${tools.getHostName()}.cli`; + obj.ts = new Date().getTime(); + + objects.setObject(SYSTEM_CONFIG_ID, obj, err => { + if (err) { + console.log(`not found: ${err.message}`); + return void callback(EXIT_CODES.CANNOT_SET_OBJECT); + } + console.log(`${SYSTEM_CONFIG_ID} reset`); return void callback(); - } - }); + }); + } else { + console.log(`${SYSTEM_CONFIG_ID} is OK`); + return void callback(); + } }); }); } diff --git a/packages/cli/src/lib/setup/setupRepo.ts b/packages/cli/src/lib/setup/setupRepo.ts index 3ead9b1e1b..b5e2097830 100644 --- a/packages/cli/src/lib/setup/setupRepo.ts +++ b/packages/cli/src/lib/setup/setupRepo.ts @@ -3,26 +3,57 @@ import axios from 'axios'; import fs from 'fs-extra'; import type { Client as ObjectsRedisClient } from '@iobroker/db-objects-redis'; import type { Client as StatesRedisClient } from '@iobroker/db-states-redis'; -import { isVersionIgnored } from '@/lib/setup/utils.js'; +import { BETA_REPO_URL, isIntegerLikeInput, isVersionIgnored, STABLE_REPO_URL } from '@/lib/setup/utils.js'; import path from 'node:path'; +import { SYSTEM_CONFIG_ID, SYSTEM_REPOSITORIES_ID } from '@iobroker/js-controller-common-db/constants'; +/** The options passed to the Repo constructor */ export interface CLIRepoOptions { + /** Instance of objects DB */ objects: ObjectsRedisClient; + /** Instance of states DB */ states: StatesRedisClient; } +/** + * All possible Repo Flags which can be passed to the `show` method + */ export interface RepoFlags { /** Also list not installed adapters */ a?: boolean; + /** Also list not installed adapters */ all?: boolean; /** Only list updatable adapters */ u?: boolean; + /** Only list updatable adapters */ updatable?: boolean; /** Force update even if hash hasn't changed */ f?: boolean; + /** Force update even if hash hasn't changed */ force?: boolean; } +type RepoActiveInfo = + | { + /** If the repo is active */ + isActive: true; + /** Name of the repository */ + name: string; + } + | { + /** If the repo is active */ + isActive: false; + }; + +interface RepoActiveOptions { + /** Url to check if active */ + url: string; + /** System config object */ + sysConfObj: ioBroker.SystemConfigObject; + /** Repository object */ + repoObj: ioBroker.RepositoryObject; +} + export class Repo { private readonly defaultSystemRepo: ioBroker.RepositoryObject; private readonly objects: ObjectsRedisClient; @@ -51,27 +82,47 @@ export class Repo { native: { repositories: { stable: { - link: 'http://download.iobroker.net/sources-dist.json', + link: STABLE_REPO_URL, json: null, }, beta: { - link: 'http://download.iobroker.net/sources-dist-latest.json', + link: BETA_REPO_URL, json: null, }, }, }, - _id: 'system.repositories', + _id: SYSTEM_REPOSITORIES_ID, type: 'config', }; } + /** + * Get the repository name by index + * Throws if the index does not exist + * + * @param repoIndex repo name or index + */ + async getNameByIndex(repoIndex: number): Promise { + const obj = await this.objects.getObject(SYSTEM_REPOSITORIES_ID); + if (!obj?.native?.repositories) { + throw new Error('No repositories setup!'); + } + + const repoNames = Object.keys(obj.native.repositories); + if (!repoNames[repoIndex]) { + throw new Error(`No repository for index "${repoIndex}" found`); + } + + return repoNames[repoIndex]; + } + /** * Update the given repository and returns new repo content * * @param repoName name of the repository * @param force force update even if same hash - * @param systemConfig content of system.config object - * @param systemRepos content of system.repositories object + * @param systemConfig content of `system.config` object + * @param systemRepos content of `system.repositories` object */ private async updateRepo( repoName: string, @@ -80,13 +131,13 @@ export class Repo { systemRepos?: ioBroker.RepositoryObject, ): Promise { if (!repoName) { - const sysConfig = systemConfig || (await this.objects.getObject('system.config')); + const sysConfig = systemConfig || (await this.objects.getObject(SYSTEM_CONFIG_ID)); repoName = sysConfig!.common.activeRepo; } - const oldRepos = systemRepos || (await this.objects.getObject('system.repositories')); + const oldRepos = systemRepos || (await this.objects.getObject(SYSTEM_REPOSITORIES_ID)); if (!oldRepos?.native.repositories?.[repoName]) { - console.log(`Error: repository "${repoName}" not found in the "system.repositories`); + console.log(`Error: repository "${repoName}" not found in the "${SYSTEM_REPOSITORIES_ID}"`); return null; } @@ -161,7 +212,7 @@ export class Repo { if (changed) { oldRepos.from = `system.host.${tools.getHostName()}.cli`; oldRepos.ts = Date.now(); - await this.objects.setObject('system.repositories', oldRepos); + await this.objects.setObject(SYSTEM_REPOSITORIES_ID, oldRepos); } return oldRepos.native.repositories[repoName].json; @@ -175,14 +226,14 @@ export class Repo { */ async showRepo(repoUrl: string | string[], flags: RepoFlags): Promise { // Get the repositories - const systemConfig = await this.objects.getObject('system.config'); - const systemRepos = await this.objects.getObject('system.repositories'); + const systemConfig = await this.objects.getObject(SYSTEM_CONFIG_ID); + const systemRepos = await this.objects.getObject(SYSTEM_REPOSITORIES_ID); if (!systemConfig) { - console.error('Error: Object "system.config" not found'); + console.error(`Error: Object "${SYSTEM_CONFIG_ID}" not found`); } else if (!systemRepos) { - console.error('Error: Object "system.repositories" not found'); + console.error(`Error: Object "${SYSTEM_REPOSITORIES_ID}" not found`); } else if (!systemRepos.native || !systemRepos.native.repositories) { - console.error('Error: no repositories found in the "system.config'); + console.error(`Error: no repositories found in the "${SYSTEM_REPOSITORIES_ID}"`); } else { repoUrl = repoUrl || systemConfig.common.activeRepo; @@ -231,7 +282,7 @@ export class Repo { /** * Show the repo result on CLI * - * @param sources Repo json sources + * @param sources Repo JSON sources * @param flags CLI flags */ private async showRepoResult(sources: Record, flags: RepoFlags): Promise { @@ -256,7 +307,7 @@ export class Repo { if (installed[name]?.version) { text += `, installed ${installed[name].version}`; try { - // tools.upToDate can throw if version is invalid + // tools.upToDate can throw if a version is invalid if ( sources[name].version !== installed[name].version && sources[name].version && @@ -295,12 +346,12 @@ export class Repo { for (const name of Object.keys(sources)) { if (installed[name] && installed[name].version && sources[name].version) { try { - // tools.upToDate can throw if version is invalid + // tools.upToDate can throw if a version is invalid if ( sources[name].version !== installed[name].version && !tools.upToDate(sources[name].version, installed[name].version) ) { - // remove first part of the name + // remove the first part of the name const n = name.indexOf('.'); list.push(n === -1 ? name : name.substring(n + 1)); } @@ -327,12 +378,12 @@ export class Repo { } /** - * Show current status of Repo on CLI + * Show the current status of Repo on CLI */ async showRepoStatus(): Promise { try { - const obj = await this.objects.getObject('system.repositories'); - const objCfg = await this.objects.getObject('system.config'); + const obj = await this.objects.getObject(SYSTEM_REPOSITORIES_ID); + const objCfg = await this.objects.getObject(SYSTEM_CONFIG_ID); if (!obj) { console.error('List is empty'); @@ -373,19 +424,25 @@ export class Repo { * @param repoUrl url of new repo */ async add(repoName: string, repoUrl: string): Promise { - const sysRepoObj = await this.objects.getObjectAsync('system.repositories'); + const sysRepoObj = await this.objects.getObject(SYSTEM_REPOSITORIES_ID); const obj = sysRepoObj || this.defaultSystemRepo; if (obj.native.repositories[repoName]) { throw new Error(`Repository "${repoName}" yet exists: ${obj.native.repositories[repoName].link}`); } else { + if (isIntegerLikeInput(repoName)) { + throw new Error( + `Invalid name "${repoName}"! A repository name is not allowed to contain only numbers.`, + ); + } + obj.native.repositories[repoName] = { link: repoUrl, json: null, }; obj.from = `system.host.${tools.getHostName()}.cli`; obj.ts = Date.now(); - await this.objects.setObjectAsync('system.repositories', obj); + await this.objects.setObject(SYSTEM_REPOSITORIES_ID, obj); } } @@ -395,7 +452,7 @@ export class Repo { * @param repoName name of repository to remove */ async del(repoName: string): Promise { - const obj = await this.objects.getObjectAsync('system.config'); + const obj = await this.objects.getObject(SYSTEM_CONFIG_ID); if ( (obj?.common.activeRepo && typeof obj.common.activeRepo === 'string' && @@ -404,7 +461,7 @@ export class Repo { ) { throw new Error(`Cannot delete active repository: ${repoName}`); } else { - const repoObj = await this.objects.getObjectAsync('system.repositories'); + const repoObj = await this.objects.getObject(SYSTEM_REPOSITORIES_ID); if (repoObj) { if (!repoObj.native.repositories[repoName]) { throw new Error(`Repository "${repoName}" not found.`); @@ -412,36 +469,84 @@ export class Repo { delete repoObj.native.repositories[repoName]; repoObj.from = `system.host.${tools.getHostName()}.cli`; repoObj.ts = Date.now(); - await this.objects.setObject('system.repositories', repoObj); + await this.objects.setObject(SYSTEM_REPOSITORIES_ID, repoObj); } } } } + /** + * Checks if stable repo is active + * + * @param options the system config object and the repository object + */ + private getRepoActiveInfo(options: RepoActiveOptions): RepoActiveInfo { + const { repoObj, sysConfObj, url } = options; + + const name = sysConfObj.common.activeRepo.find( + activeRepo => repoObj.native.repositories[activeRepo].link === url, + ); + + return name ? { isActive: true, name } : { isActive: false }; + } + /** * Set specific repo as active one * - * @param repoName name of the respository to activate + * @param repoName name of the repository to activate */ async setActive(repoName: string): Promise { - const sysRepoObj = await this.objects.getObjectAsync('system.repositories'); + const sysRepoObj = await this.objects.getObject(SYSTEM_REPOSITORIES_ID); const obj = sysRepoObj || this.defaultSystemRepo; if (!obj.native.repositories[repoName]) { throw new Error(`Repository "${repoName}" not found.`); - } else { - const confObj = await this.objects.getObjectAsync('system.config'); - if (typeof confObj?.common.activeRepo === 'string') { - confObj.common.activeRepo = [confObj.common.activeRepo]; - } + } + + const confObj = await this.objects.getObject(SYSTEM_CONFIG_ID); + if (typeof confObj?.common.activeRepo === 'string') { + confObj.common.activeRepo = [confObj.common.activeRepo]; + } - if (confObj && !confObj.common.activeRepo.includes(repoName)) { - confObj.common.activeRepo.push(repoName); - confObj.from = `system.host.${tools.getHostName()}.cli`; - confObj.ts = Date.now(); - await this.objects.setObjectAsync('system.config', confObj); + if (!confObj || confObj.common.activeRepo.includes(repoName)) { + return; + } + + confObj.common.activeRepo.push(repoName); + + const stableInfo = this.getRepoActiveInfo({ + url: STABLE_REPO_URL, + sysConfObj: confObj, + repoObj: obj, + }); + + const betaInfo = this.getRepoActiveInfo({ + url: BETA_REPO_URL, + sysConfObj: confObj, + repoObj: obj, + }); + + if (stableInfo.isActive && betaInfo.isActive) { + const posStable = confObj.common.activeRepo.indexOf(stableInfo.name); + const posBeta = confObj.common.activeRepo.indexOf(betaInfo.name); + + if (posStable < posBeta) { + // put beta in front of stable, so stable will override adapters + confObj.common.activeRepo.splice(posBeta, 1); + confObj.common.activeRepo.splice(posStable, 0, betaInfo.name); + console.info( + `Ensured that stable repository "${stableInfo.name}" has priority over beta repository "${betaInfo.name}"`, + ); } } + + if (confObj.common.activeRepo.length > 1) { + console.info('More than one repository is active. Please be sure, that this is a desired constellation!'); + } + + confObj.from = `system.host.${tools.getHostName()}.cli`; + confObj.ts = Date.now(); + await this.objects.setObject(SYSTEM_CONFIG_ID, confObj); } /** @@ -450,7 +555,7 @@ export class Repo { * @param repoName name of the repository */ async setInactive(repoName: string): Promise { - const confObj = (await this.objects.getObjectAsync('system.config'))!; + const confObj = (await this.objects.getObject(SYSTEM_CONFIG_ID))!; if (typeof confObj?.common.activeRepo === 'string') { confObj.common.activeRepo = [confObj.common.activeRepo]; } @@ -460,12 +565,12 @@ export class Repo { confObj.common.activeRepo.splice(pos, 1); confObj.from = `system.host.${tools.getHostName()}.cli`; confObj.ts = Date.now(); - await this.objects.setObjectAsync('system.config', confObj); + await this.objects.setObject(SYSTEM_CONFIG_ID, confObj); } } /** - * Renames existing repository if old name and link matches, renaming will not be performed if an repo with the new name already exists + * Renames existing repository if old name and link matches, renaming will not be performed if a repo with the new name already exists * * @param oldName - name of the current repository * @param newName - target name @@ -475,48 +580,51 @@ export class Repo { let repoObj; let sysConfigObj; try { - sysConfigObj = await this.objects.getObjectAsync('system.config'); - repoObj = await this.objects.getObjectAsync('system.repositories'); + sysConfigObj = await this.objects.getObject(SYSTEM_CONFIG_ID); + repoObj = await this.objects.getObject(SYSTEM_REPOSITORIES_ID); } catch (err) { throw new Error(`Could not rename repository "${oldName}" to "${newName}": ${err.message}`); } - if (repoObj && repoObj.native && repoObj.native.repositories) { + if (isIntegerLikeInput(newName)) { + throw new Error(`Invalid name "${newName}"! A repository name is not allowed to contain only numbers.`); + } + + if (!repoObj) { + return; + } + + if ( + repoObj.native.repositories[oldName] && + repoObj.native.repositories[oldName].link === repoUrl && + !repoObj.native.repositories[newName] + ) { + repoObj.native.repositories[newName] = repoObj.native.repositories[oldName]; + delete repoObj.native.repositories[oldName]; + + try { + await this.objects.setObject(SYSTEM_REPOSITORIES_ID, repoObj); + console.log(`Renamed repository "${oldName} to "${newName}"`); + } catch (e) { + throw new Error(`Could not rename repository "${oldName}" to "${newName}": ${e.message}`); + } + + // if we changed the name of the activeRepo, we should set newName as active repo if ( - repoObj.native.repositories[oldName] && - repoObj.native.repositories[oldName].link === repoUrl && - !repoObj.native.repositories[newName] + sysConfigObj?.common && + ((typeof sysConfigObj.common.activeRepo === 'string' && sysConfigObj.common.activeRepo === oldName) || + (Array.isArray(sysConfigObj.common.activeRepo) && sysConfigObj.common.activeRepo.includes(oldName))) ) { - repoObj.native.repositories[newName] = repoObj.native.repositories[oldName]; - delete repoObj.native.repositories[oldName]; - - try { - await this.objects.setObjectAsync('system.repositories', repoObj); - console.log(`Renamed repository "${oldName} to "${newName}"`); - } catch (err) { - throw new Error(`Could not rename repository "${oldName}" to "${newName}": ${err.message}`); + if (typeof sysConfigObj.common.activeRepo === 'string') { + sysConfigObj.common.activeRepo = [sysConfigObj.common.activeRepo]; } + const pos = sysConfigObj.common.activeRepo.indexOf(oldName); + sysConfigObj.common.activeRepo.splice(pos, 1, newName); - // if we changed the name of the activeRepo, we should set newName as active repo - if ( - sysConfigObj && - sysConfigObj.common && - ((typeof sysConfigObj.common.activeRepo === 'string' && - sysConfigObj.common.activeRepo === oldName) || - (Array.isArray(sysConfigObj.common.activeRepo) && - sysConfigObj.common.activeRepo.includes(oldName))) - ) { - if (typeof sysConfigObj.common.activeRepo === 'string') { - sysConfigObj.common.activeRepo = [sysConfigObj.common.activeRepo]; - } - const pos = sysConfigObj.common.activeRepo.indexOf(oldName); - sysConfigObj.common.activeRepo.splice(pos, 1, newName); - - try { - await this.objects.setObjectAsync('system.config', sysConfigObj); - } catch (err) { - throw new Error(`Could not set "${newName}" as active repository: ${err.message}`); - } + try { + await this.objects.setObject(SYSTEM_CONFIG_ID, sysConfigObj); + } catch (e) { + throw new Error(`Could not set "${newName}" as active repository: ${e.message}`); } } } diff --git a/packages/cli/src/lib/setup/utils.ts b/packages/cli/src/lib/setup/utils.ts index 88e9c3f2d3..bfc257e993 100644 --- a/packages/cli/src/lib/setup/utils.ts +++ b/packages/cli/src/lib/setup/utils.ts @@ -153,3 +153,18 @@ export async function recognizeVersion(options: VersionOptions): Promise { await objects.setObject(id, obj); } + +/** + * Check if user input is an integer + * + * @param input input string to check + */ +export function isIntegerLikeInput(input: string): boolean { + return /^\d+$/.test(input); +} + +/** URL of the official stable repository */ +export const STABLE_REPO_URL = 'http://download.iobroker.net/sources-dist.json'; + +/** URL of the official beta repository */ +export const BETA_REPO_URL = 'http://download.iobroker.net/sources-dist-latest.json';