From 8105af33a7d9d89fa142460e99febe17a043ea7c Mon Sep 17 00:00:00 2001 From: Michel Hopfner Date: Wed, 26 Jul 2023 17:21:48 +0200 Subject: [PATCH] Hard typing and seperating flags from install script --- .idea/inspectionProfiles/Project_Default.xml | 4 +- src/commands/app/install/contao.tsx | 141 -------------- src/commands/app/install/joomla.tsx | 141 -------------- src/commands/app/install/matomo.tsx | 131 ------------- src/commands/app/install/node.tsx | 99 ---------- src/commands/app/install/php.tsx | 99 ---------- src/commands/app/install/shopware5.tsx | 156 --------------- src/commands/app/install/shopware6.tsx | 114 ++++------- src/commands/app/install/typo3.tsx | 131 ------------- src/commands/app/install/wordpress.tsx | 131 ------------- src/lib/app/flags.tsx | 191 ++++++++++++++++--- src/lib/app/{create.ts => install.ts} | 27 +-- src/lib/password.ts | 31 +++ src/rendering/react/process_flags.ts | 1 - 14 files changed, 235 insertions(+), 1162 deletions(-) delete mode 100644 src/commands/app/install/contao.tsx delete mode 100644 src/commands/app/install/joomla.tsx delete mode 100644 src/commands/app/install/matomo.tsx delete mode 100644 src/commands/app/install/node.tsx delete mode 100644 src/commands/app/install/php.tsx delete mode 100644 src/commands/app/install/shopware5.tsx delete mode 100644 src/commands/app/install/typo3.tsx delete mode 100644 src/commands/app/install/wordpress.tsx rename src/lib/app/{create.ts => install.ts} (54%) create mode 100644 src/lib/password.ts diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index 9c6941105..522dcb320 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -1,7 +1,9 @@ \ No newline at end of file diff --git a/src/commands/app/install/contao.tsx b/src/commands/app/install/contao.tsx deleted file mode 100644 index e77b80452..000000000 --- a/src/commands/app/install/contao.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import { Flags } from "@oclif/core"; -import { normalizeToAppVersionUuid } from "../../../lib/app/versions.js"; -import { autofillFlags } from "../../../lib/app/flags.js"; -import { waitUntilAppIsInstalled } from "../../../lib/app/wait.js"; -import { MittwaldAPIV2 } from "@mittwald/api-client"; -import { projectFlags, withProjectId } from "../../../lib/project/flags.js"; -import { ExecRenderBaseCommand } from "../../../rendering/react/ExecRenderBaseCommand.js"; -import { - makeProcessRenderer, - processFlags, -} from "../../../rendering/react/process_flags.js"; -import { Success } from "../../../rendering/react/components/Success.js"; -import React from "react"; -import AppAppVersion = MittwaldAPIV2.Components.Schemas.AppAppVersion; -import { triggerAppInstallation } from "../../../lib/app/create.js"; - -export default class AppCreateContao extends ExecRenderBaseCommand< - typeof AppCreateContao, - { appInstallationId: string } -> { - static appName: string = "Contao"; - static appUuid: string = "4916ce3e-cba4-4d2e-9798-a8764aa14cf3"; - static appNecessaryFlags: string[] = [ - "version", - "host", - "admin-user", - "admin-email", - "admin-pass", - "admin-firstname", - "admin-lastname", - "site-title", - ]; - - static description: string = `Creates new ${AppCreateContao.appName} Installation.`; - static flags = { - ...projectFlags, - ...processFlags, - version: Flags.string({ - required: true, - description: `Version of the ${AppCreateContao.appName} to be created - Defaults to latest`, - default: "latest", - }), - host: Flags.string({ - required: false, - description: `Host under which your ${AppCreateContao.appName} will be available (Needs to be created separately).`, - }), - "admin-user": Flags.string({ - required: false, - description: `First Admin User for your ${AppCreateContao.appName}.`, - }), - "admin-email": Flags.string({ - required: false, - description: "First Admin Users E-Mail.", - }), - "admin-pass": Flags.string({ - required: false, - description: "First Admin Users Password.", - }), - "admin-firstname": Flags.string({ - required: false, - description: "First Admin Users Lastname.", - }), - "admin-lastname": Flags.string({ - required: false, - description: `Site Title of the created ${AppCreateContao.appName}.`, - }), - "site-title": Flags.string({ - required: false, - description: `Site Title of the created ${AppCreateContao.appName}.`, - }), - wait: Flags.boolean({ - char: "w", - description: `Wait for your ${AppCreateContao.appName} to be ready.`, - }), - }; - - protected async exec(): Promise<{ appInstallationId: string }> { - const process = makeProcessRenderer( - this.flags, - `Installing ${AppCreateContao.appName}`, - ); - let { flags, args } = await this.parse(AppCreateContao); - const projectId = await withProjectId( - this.apiClient, - flags, - args, - this.config, - ); - - flags = await autofillFlags( - this.apiClient, - process, - AppCreateContao.appNecessaryFlags, - flags, - projectId, - AppCreateContao.appName, - ); - - const appVersion: AppAppVersion = await normalizeToAppVersionUuid( - this.apiClient, - flags.version, - process, - AppCreateContao.appUuid, - ); - - const [appInstallationId, eventId] = await triggerAppInstallation( - this.apiClient, - process, - projectId, - flags, - appVersion, - ); - - let successText: string; - if (flags.wait) { - await waitUntilAppIsInstalled( - this.apiClient, - process, - appInstallationId, - eventId, - ); - successText = `Your ${AppCreateContao.appName} installation is now complete. Have fun! 🎉`; - } else { - successText = `Your ${AppCreateContao.appName} installation has started. Have fun when it's ready! 🎉`; - } - - process.complete({successText}); - return { appInstallationId }; - } - - protected render({ - appInstallationId, - }: { - appInstallationId: string; - }): React.ReactNode { - if (this.flags.quiet) { - this.log(appInstallationId); - } - return undefined; - } -} diff --git a/src/commands/app/install/joomla.tsx b/src/commands/app/install/joomla.tsx deleted file mode 100644 index af3807f71..000000000 --- a/src/commands/app/install/joomla.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import { Flags } from "@oclif/core"; -import { normalizeToAppVersionUuid } from "../../../lib/app/versions.js"; -import { autofillFlags } from "../../../lib/app/flags.js"; -import { waitUntilAppIsInstalled } from "../../../lib/app/wait.js"; -import { MittwaldAPIV2 } from "@mittwald/api-client"; -import { projectFlags, withProjectId } from "../../../lib/project/flags.js"; -import { ExecRenderBaseCommand } from "../../../rendering/react/ExecRenderBaseCommand.js"; -import { - makeProcessRenderer, - processFlags, -} from "../../../rendering/react/process_flags.js"; -import { Success } from "../../../rendering/react/components/Success.js"; -import React from "react"; -import AppAppVersion = MittwaldAPIV2.Components.Schemas.AppAppVersion; -import { triggerAppInstallation } from "../../../lib/app/create.js"; - -export default class AppCreateShopware6 extends ExecRenderBaseCommand< - typeof AppCreateShopware6, - { appInstallationId: string } -> { - static appName: string = "Joomla!"; - static appUuid: string = "8d404bff-6d75-4833-9eed-1b83b0552585"; - static appNecessaryFlags: string[] = [ - "version", - "host", - "admin-user", - "admin-email", - "admin-pass", - "admin-firstname", - "admin-lastname", - "site-title", - ]; - - static description: string = `Creates new ${AppCreateShopware6.appName} Installation.`; - static flags = { - ...projectFlags, - ...processFlags, - version: Flags.string({ - required: true, - description: `Version of the ${AppCreateShopware6.appName} to be created - Defaults to latest`, - default: "latest", - }), - host: Flags.string({ - required: false, - description: `Host under which your ${AppCreateShopware6.appName} will be available (Needs to be created separately).`, - }), - "admin-user": Flags.string({ - required: false, - description: `First Admin User for your ${AppCreateShopware6.appName}.`, - }), - "admin-email": Flags.string({ - required: false, - description: "First Admin Users E-Mail.", - }), - "admin-pass": Flags.string({ - required: false, - description: "First Admin Users Password.", - }), - "admin-firstname": Flags.string({ - required: false, - description: "First Admin Users Lastname.", - }), - "admin-lastname": Flags.string({ - required: false, - description: `Site Title of the created ${AppCreateShopware6.appName}.`, - }), - "site-title": Flags.string({ - required: false, - description: `Site Title of the created ${AppCreateShopware6.appName}.`, - }), - wait: Flags.boolean({ - char: "w", - description: `Wait for your ${AppCreateShopware6.appName} to be ready.`, - }), - }; - - protected async exec(): Promise<{ appInstallationId: string }> { - const process = makeProcessRenderer( - this.flags, - `Installing ${AppCreateShopware6.appName}`, - ); - let { flags, args } = await this.parse(AppCreateShopware6); - const projectId = await withProjectId( - this.apiClient, - flags, - args, - this.config, - ); - - flags = await autofillFlags( - this.apiClient, - process, - AppCreateShopware6.appNecessaryFlags, - flags, - projectId, - AppCreateShopware6.appName, - ); - - const appVersion: AppAppVersion = await normalizeToAppVersionUuid( - this.apiClient, - flags.version, - process, - AppCreateShopware6.appUuid, - ); - - const [appInstallationId, eventId] = await triggerAppInstallation( - this.apiClient, - process, - projectId, - flags, - appVersion, - ); - - let successText: string; - if (flags.wait) { - await waitUntilAppIsInstalled( - this.apiClient, - process, - appInstallationId, - eventId, - ); - successText = `Your ${AppCreateShopware6.appName} installation is now complete. Have fun! 🎉`; - } else { - successText = `Your ${AppCreateShopware6.appName} installation has started. Have fun when it's ready! 🎉`; - } - - process.complete({successText}); - return { appInstallationId }; - } - - protected render({ - appInstallationId, - }: { - appInstallationId: string; - }): React.ReactNode { - if (this.flags.quiet) { - this.log(appInstallationId); - } - return undefined; - } -} diff --git a/src/commands/app/install/matomo.tsx b/src/commands/app/install/matomo.tsx deleted file mode 100644 index b6a208a06..000000000 --- a/src/commands/app/install/matomo.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import { Flags } from "@oclif/core"; -import { normalizeToAppVersionUuid } from "../../../lib/app/versions.js"; -import { autofillFlags } from "../../../lib/app/flags.js"; -import { waitUntilAppIsInstalled } from "../../../lib/app/wait.js"; -import { MittwaldAPIV2 } from "@mittwald/api-client"; -import { projectFlags, withProjectId } from "../../../lib/project/flags.js"; -import { ExecRenderBaseCommand } from "../../../rendering/react/ExecRenderBaseCommand.js"; -import { - makeProcessRenderer, - processFlags, -} from "../../../rendering/react/process_flags.js"; -import { Success } from "../../../rendering/react/components/Success.js"; -import React from "react"; -import AppAppVersion = MittwaldAPIV2.Components.Schemas.AppAppVersion; -import { triggerAppInstallation } from "../../../lib/app/create.js"; - -export default class AppCreateMatomo extends ExecRenderBaseCommand< - typeof AppCreateMatomo, - { appInstallationId: string } -> { - static appName: string = "Matomo"; - static appUuid: string = "91fa05e7-34f7-42e8-a8d3-a9c42abd5f8c"; - static appNecessaryFlags: string[] = [ - "version", - "host", - "admin-user", - "admin-email", - "admin-pass", - "site-title", - ]; - - static description: string = `Creates new ${AppCreateMatomo.appName} Installation.`; - static flags = { - ...projectFlags, - ...processFlags, - version: Flags.string({ - required: true, - description: `Version of the ${AppCreateMatomo.appName} to be created - Defaults to latest`, - default: "latest", - }), - host: Flags.string({ - required: false, - description: `Host under which your ${AppCreateMatomo.appName} will be available (Needs to be created separately).`, - }), - "admin-user": Flags.string({ - required: false, - description: `First Admin User for your ${AppCreateMatomo.appName}.`, - }), - "admin-email": Flags.string({ - required: false, - description: "First Admin Users E-Mail.", - }), - "admin-pass": Flags.string({ - required: false, - description: "First Admin Users Password.", - }), - "site-title": Flags.string({ - required: false, - description: `Site Title of the created ${AppCreateMatomo.appName}.`, - }), - wait: Flags.boolean({ - char: "w", - description: `Wait for your ${AppCreateMatomo.appName} to be ready.`, - }), - }; - - protected async exec(): Promise<{ appInstallationId: string }> { - const process = makeProcessRenderer( - this.flags, - `Installing ${AppCreateMatomo.appName}`, - ); - let { flags, args } = await this.parse(AppCreateMatomo); - const projectId = await withProjectId( - this.apiClient, - flags, - args, - this.config, - ); - - flags = await autofillFlags( - this.apiClient, - process, - AppCreateMatomo.appNecessaryFlags, - flags, - projectId, - AppCreateMatomo.appName, - ); - - const appVersion: AppAppVersion = await normalizeToAppVersionUuid( - this.apiClient, - flags.version, - process, - AppCreateMatomo.appUuid, - ); - - const [appInstallationId, eventId] = await triggerAppInstallation( - this.apiClient, - process, - projectId, - flags, - appVersion, - ); - - let successText: string; - if (flags.wait) { - await waitUntilAppIsInstalled( - this.apiClient, - process, - appInstallationId, - eventId, - ); - successText = `Your ${AppCreateMatomo.appName} installation is now complete. Have fun! 🎉`; - } else { - successText = `Your ${AppCreateMatomo.appName} installation has started. Have fun when it's ready! 🎉`; - } - - process.complete({successText}); - return { appInstallationId }; - } - - protected render({ - appInstallationId, - }: { - appInstallationId: string; - }): React.ReactNode { - if (this.flags.quiet) { - this.log(appInstallationId); - } - return undefined; - } -} diff --git a/src/commands/app/install/node.tsx b/src/commands/app/install/node.tsx deleted file mode 100644 index 82ccf770f..000000000 --- a/src/commands/app/install/node.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { Flags } from "@oclif/core"; -import { normalizeToAppVersionUuid } from "../../../lib/app/versions.js"; -import { autofillFlags } from "../../../lib/app/flags.js"; -import { waitUntilAppIsInstalled } from "../../../lib/app/wait.js"; -import { MittwaldAPIV2 } from "@mittwald/api-client"; -import { projectFlags, withProjectId } from "../../../lib/project/flags.js"; -import { ExecRenderBaseCommand } from "../../../rendering/react/ExecRenderBaseCommand.js"; -import { - makeProcessRenderer, - processFlags, -} from "../../../rendering/react/process_flags.js"; -import { Success } from "../../../rendering/react/components/Success.js"; -import React from "react"; -import AppAppVersion = MittwaldAPIV2.Components.Schemas.AppAppVersion; -import { triggerAppInstallation } from "../../../lib/app/create.js"; - -export default class AppCreateNodeJs extends ExecRenderBaseCommand< - typeof AppCreateNodeJs, - { appInstallationId: string } -> { - static appName: string = "Node.js"; - static appUuid: string = "3e7f920b-a711-4d2f-9871-661e1b41a2f0"; - static appNecessaryFlags: string[] = ["site-title"]; - - static description: string = `Creates new ${AppCreateNodeJs.appName} Installation.`; - static flags = { - ...projectFlags, - ...processFlags, - "site-title": Flags.string({ - required: false, - description: `Site Title of the created ${AppCreateNodeJs.appName}.`, - }), - }; - - protected async exec(): Promise<{ appInstallationId: string }> { - const process = makeProcessRenderer( - this.flags, - `Installing ${AppCreateNodeJs.appName}`, - ); - let { flags, args } = await this.parse(AppCreateNodeJs); - const projectId = await withProjectId( - this.apiClient, - flags, - args, - this.config, - ); - - flags = await autofillFlags( - this.apiClient, - process, - AppCreateNodeJs.appNecessaryFlags, - flags, - projectId, - AppCreateNodeJs.appName, - ); - - const appVersion: AppAppVersion = await normalizeToAppVersionUuid( - this.apiClient, - flags.version, - process, - AppCreateNodeJs.appUuid, - ); - - const [appInstallationId, eventId] = await triggerAppInstallation( - this.apiClient, - process, - projectId, - flags, - appVersion, - ); - - let successText: string; - if (flags.wait) { - await waitUntilAppIsInstalled( - this.apiClient, - process, - appInstallationId, - eventId, - ); - successText = `Your ${AppCreateNodeJs.appName} installation is now complete. Have fun! 🎉`; - } else { - successText = `Your ${AppCreateNodeJs.appName} installation has started. Have fun when it's ready! 🎉`; - } - - process.complete({successText}); - return { appInstallationId }; - } - - protected render({ - appInstallationId, - }: { - appInstallationId: string; - }): React.ReactNode { - if (this.flags.quiet) { - this.log(appInstallationId); - } - return undefined; - } -} diff --git a/src/commands/app/install/php.tsx b/src/commands/app/install/php.tsx deleted file mode 100644 index 6299bd43f..000000000 --- a/src/commands/app/install/php.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { Flags } from "@oclif/core"; -import { normalizeToAppVersionUuid } from "../../../lib/app/versions.js"; -import { autofillFlags } from "../../../lib/app/flags.js"; -import { waitUntilAppIsInstalled } from "../../../lib/app/wait.js"; -import { MittwaldAPIV2 } from "@mittwald/api-client"; -import { projectFlags, withProjectId } from "../../../lib/project/flags.js"; -import { ExecRenderBaseCommand } from "../../../rendering/react/ExecRenderBaseCommand.js"; -import { - makeProcessRenderer, - processFlags, -} from "../../../rendering/react/process_flags.js"; -import { Success } from "../../../rendering/react/components/Success.js"; -import React from "react"; -import AppAppVersion = MittwaldAPIV2.Components.Schemas.AppAppVersion; -import { triggerAppInstallation } from "../../../lib/app/create.js"; - -export default class AppCreatePhp extends ExecRenderBaseCommand< - typeof AppCreatePhp, - { appInstallationId: string } -> { - static appName: string = "PHP"; - static appUuid: string = "34220303-cb87-4592-8a95-2eb20a97b2ac"; - static appNecessaryFlags: string[] = ["site-title"]; - - static description: string = `Creates new ${AppCreatePhp.appName} Installation.`; - static flags = { - ...projectFlags, - ...processFlags, - "site-title": Flags.string({ - required: false, - description: `Site Title of the created ${AppCreatePhp.appName}.`, - }), - }; - - protected async exec(): Promise<{ appInstallationId: string }> { - const process = makeProcessRenderer( - this.flags, - `Installing ${AppCreatePhp.appName}`, - ); - let { flags, args } = await this.parse(AppCreatePhp); - const projectId = await withProjectId( - this.apiClient, - flags, - args, - this.config, - ); - - flags = await autofillFlags( - this.apiClient, - process, - AppCreatePhp.appNecessaryFlags, - flags, - projectId, - AppCreatePhp.appName, - ); - - const appVersion: AppAppVersion = await normalizeToAppVersionUuid( - this.apiClient, - flags.version, - process, - AppCreatePhp.appUuid, - ); - - const [appInstallationId, eventId] = await triggerAppInstallation( - this.apiClient, - process, - projectId, - flags, - appVersion, - ); - - let successText: string; - if (flags.wait) { - await waitUntilAppIsInstalled( - this.apiClient, - process, - appInstallationId, - eventId, - ); - successText = `Your ${AppCreatePhp.appName} installation is now complete. Have fun! 🎉`; - } else { - successText = `Your ${AppCreatePhp.appName} installation has started. Have fun when it's ready! 🎉`; - } - - process.complete({successText}); - return { appInstallationId }; - } - - protected render({ - appInstallationId, - }: { - appInstallationId: string; - }): React.ReactNode { - if (this.flags.quiet) { - this.log(appInstallationId); - } - return undefined; - } -} diff --git a/src/commands/app/install/shopware5.tsx b/src/commands/app/install/shopware5.tsx deleted file mode 100644 index c6cb3c910..000000000 --- a/src/commands/app/install/shopware5.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import { Flags } from "@oclif/core"; -import { normalizeToAppVersionUuid } from "../../../lib/app/versions.js"; -import { autofillFlags } from "../../../lib/app/flags.js"; -import { waitUntilAppIsInstalled } from "../../../lib/app/wait.js"; -import { MittwaldAPIV2 } from "@mittwald/api-client"; -import { projectFlags, withProjectId } from "../../../lib/project/flags.js"; -import { ExecRenderBaseCommand } from "../../../rendering/react/ExecRenderBaseCommand.js"; -import { - makeProcessRenderer, - processFlags, -} from "../../../rendering/react/process_flags.js"; -import { Success } from "../../../rendering/react/components/Success.js"; -import React from "react"; -import AppAppVersion = MittwaldAPIV2.Components.Schemas.AppAppVersion; -import { triggerAppInstallation } from "../../../lib/app/create.js"; - -export default class AppCreateShopware5 extends ExecRenderBaseCommand< - typeof AppCreateShopware5, - { appInstallationId: string } -> { - static appName: string = "Shopware5"; - static appUuid: string = "a23acf9c-9298-4082-9e7d-25356f9976dc"; - static appNecessaryFlags: string[] = [ - "version", - "host", - "admin-user", - "admin-email", - "admin-pass", - "admin-firstname", - "admin-lastname", - "site-title", - "shop-email", - "shop-language", - "shop-currency", - ]; - - static description: string = `Creates new ${AppCreateShopware5.appName} Installation.`; - static flags = { - ...projectFlags, - ...processFlags, - version: Flags.string({ - required: true, - description: `Version of the ${AppCreateShopware5.appName} to be created - Defaults to latest`, - default: "latest", - }), - host: Flags.string({ - required: false, - description: `Host under which your ${AppCreateShopware5.appName} will be available (Needs to be created separately).`, - }), - "admin-user": Flags.string({ - required: false, - description: `First Admin User for your ${AppCreateShopware5.appName}.`, - }), - "admin-email": Flags.string({ - required: false, - description: "First Admin Users E-Mail.", - }), - "admin-pass": Flags.string({ - required: false, - description: "First Admin Users Password.", - }), - "admin-firstname": Flags.string({ - required: false, - description: "First Admin Users Lastname.", - }), - "admin-lastname": Flags.string({ - required: false, - description: `Site Title of the created ${AppCreateShopware5.appName}.`, - }), - "site-title": Flags.string({ - required: false, - description: `Site Title of the created ${AppCreateShopware5.appName}.`, - }), - "shop-email": Flags.string({ - required: false, - description: `E-Mail with which your ${AppCreateShopware5.appName} communicates initially.`, - }), - "shop-language": Flags.string({ - required: false, - description: `Language with which your ${AppCreateShopware5.appName} works initially`, - }), - "shop-currency": Flags.string({ - required: false, - description: `The initial currency your ${AppCreateShopware5.appName} works with.`, - }), - wait: Flags.boolean({ - char: "w", - description: `Wait for your ${AppCreateShopware5.appName} to be ready.`, - }), - }; - - protected async exec(): Promise<{ appInstallationId: string }> { - const process = makeProcessRenderer( - this.flags, - `Installing ${AppCreateShopware5.appName}`, - ); - let { flags, args } = await this.parse(AppCreateShopware5); - const projectId = await withProjectId( - this.apiClient, - flags, - args, - this.config, - ); - - flags = await autofillFlags( - this.apiClient, - process, - AppCreateShopware5.appNecessaryFlags, - flags, - projectId, - AppCreateShopware5.appName, - ); - - const appVersion: AppAppVersion = await normalizeToAppVersionUuid( - this.apiClient, - flags.version, - process, - AppCreateShopware5.appUuid, - ); - - const [appInstallationId, eventId] = await triggerAppInstallation( - this.apiClient, - process, - projectId, - flags, - appVersion, - ); - - let successText: string; - if (flags.wait) { - await waitUntilAppIsInstalled( - this.apiClient, - process, - appInstallationId, - eventId, - ); - successText = `Your ${AppCreateShopware5.appName} installation is now complete. Have fun! 🎉`; - } else { - successText = `Your ${AppCreateShopware5.appName} installation has started. Have fun when it's ready! 🎉`; - } - - process.complete({successText}); - return { appInstallationId }; - } - - protected render({ - appInstallationId, - }: { - appInstallationId: string; - }): React.ReactNode { - if (this.flags.quiet) { - this.log(appInstallationId); - } - return undefined; - } -} diff --git a/src/commands/app/install/shopware6.tsx b/src/commands/app/install/shopware6.tsx index 76392e490..153e918e8 100644 --- a/src/commands/app/install/shopware6.tsx +++ b/src/commands/app/install/shopware6.tsx @@ -1,28 +1,30 @@ -import { Flags } from "@oclif/core"; import { normalizeToAppVersionUuid } from "../../../lib/app/versions.js"; -import { autofillFlags } from "../../../lib/app/flags.js"; +import { + autofillFlags, + provideSupportedFlags, + RelevantFlagInput, +} from "../../../lib/app/flags.js"; import { waitUntilAppIsInstalled } from "../../../lib/app/wait.js"; import { MittwaldAPIV2 } from "@mittwald/api-client"; -import { projectFlags, withProjectId } from "../../../lib/project/flags.js"; +import { withProjectId } from "../../../lib/project/flags.js"; import { ExecRenderBaseCommand } from "../../../rendering/react/ExecRenderBaseCommand.js"; -import { - makeProcessRenderer, - processFlags, -} from "../../../rendering/react/process_flags.js"; +import { makeProcessRenderer } from "../../../rendering/react/process_flags.js"; import { Success } from "../../../rendering/react/components/Success.js"; import React from "react"; import AppAppVersion = MittwaldAPIV2.Components.Schemas.AppAppVersion; -import { triggerAppInstallation } from "../../../lib/app/create.js"; +import { triggerAppInstallation } from "../../../lib/app/install.js"; +import { OutputFlags } from "@oclif/core/lib/interfaces/parser.js"; -export default class AppCreateShopware6 extends ExecRenderBaseCommand< - typeof AppCreateShopware6, +export default class AppInstallation extends ExecRenderBaseCommand< + typeof AppInstallation, { appInstallationId: string } > { - static appName: string = "Shopware6"; - static appUuid: string = "12d54d05-7e55-4cf3-90c4-093516e0eaf8"; - static appNecessaryFlags: string[] = [ + static appName = "Shopware6"; + static appUuid = "12d54d05-7e55-4cf3-90c4-093516e0eaf8"; + static appSupportedFlags = [ "version", "host", + "admin-firstname", "admin-user", "admin-email", "admin-pass", @@ -30,71 +32,27 @@ export default class AppCreateShopware6 extends ExecRenderBaseCommand< "admin-lastname", "site-title", "shop-email", - "shop-language", + "shop-lang", "shop-currency", - ]; + "wait", + ] as const; - static description: string = `Creates new ${AppCreateShopware6.appName} Installation.`; - static flags = { - ...projectFlags, - ...processFlags, - version: Flags.string({ - required: true, - description: `Version of the ${AppCreateShopware6.appName} to be created - Defaults to latest`, - default: "latest", - }), - host: Flags.string({ - required: false, - description: `Host under which your ${AppCreateShopware6.appName} will be available (Needs to be created separately).`, - }), - "admin-user": Flags.string({ - required: false, - description: `First Admin User for your ${AppCreateShopware6.appName}.`, - }), - "admin-email": Flags.string({ - required: false, - description: "First Admin Users E-Mail.", - }), - "admin-pass": Flags.string({ - required: false, - description: "First Admin Users Password.", - }), - "admin-firstname": Flags.string({ - required: false, - description: "First Admin Users Lastname.", - }), - "admin-lastname": Flags.string({ - required: false, - description: `Site Title of the created ${AppCreateShopware6.appName}.`, - }), - "site-title": Flags.string({ - required: false, - description: `Site Title of the created ${AppCreateShopware6.appName}.`, - }), - "shop-email": Flags.string({ - required: false, - description: `E-Mail with which your ${AppCreateShopware6.appName} communicates initially.`, - }), - "shop-language": Flags.string({ - required: false, - description: `Language with which your ${AppCreateShopware6.appName} works initially`, - }), - "shop-currency": Flags.string({ - required: false, - description: `The initial currency your ${AppCreateShopware6.appName} works with.`, - }), - wait: Flags.boolean({ - char: "w", - description: `Wait for your ${AppCreateShopware6.appName} to be ready.`, - }), - }; + static description = `Creates new ${AppInstallation.appName} Installation.`; + static flags: RelevantFlagInput = + provideSupportedFlags( + AppInstallation.appSupportedFlags, + AppInstallation.appName, + ); protected async exec(): Promise<{ appInstallationId: string }> { const process = makeProcessRenderer( this.flags, - `Installing ${AppCreateShopware6.appName}`, + `Installing ${AppInstallation.appName}`, ); - let { flags, args } = await this.parse(AppCreateShopware6); + const parsed = await this.parse(AppInstallation); + const args = parsed.args; + const flags: OutputFlags = parsed.flags; + const projectId = await withProjectId( this.apiClient, flags, @@ -102,20 +60,20 @@ export default class AppCreateShopware6 extends ExecRenderBaseCommand< this.config, ); - flags = await autofillFlags( + await autofillFlags( this.apiClient, process, - AppCreateShopware6.appNecessaryFlags, + AppInstallation.appSupportedFlags, flags, projectId, - AppCreateShopware6.appName, + AppInstallation.appName, ); const appVersion: AppAppVersion = await normalizeToAppVersionUuid( this.apiClient, - flags.version, + flags.version as unknown as string, process, - AppCreateShopware6.appUuid, + AppInstallation.appUuid, ); const [appInstallationId, eventId] = await triggerAppInstallation( @@ -134,9 +92,9 @@ export default class AppCreateShopware6 extends ExecRenderBaseCommand< appInstallationId, eventId, ); - successText = `Your ${AppCreateShopware6.appName} installation is now complete. Have fun! 🎉`; + successText = `Your ${AppInstallation.appName} installation is now complete. Have fun! 🎉`; } else { - successText = `Your ${AppCreateShopware6.appName} installation has started. Have fun when it's ready! 🎉`; + successText = `Your ${AppInstallation.appName} installation has started. Have fun when it's ready! 🎉`; } process.complete({successText}); diff --git a/src/commands/app/install/typo3.tsx b/src/commands/app/install/typo3.tsx deleted file mode 100644 index 7728d9006..000000000 --- a/src/commands/app/install/typo3.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import { Flags } from "@oclif/core"; -import { normalizeToAppVersionUuid } from "../../../lib/app/versions.js"; -import { autofillFlags } from "../../../lib/app/flags.js"; -import { waitUntilAppIsInstalled } from "../../../lib/app/wait.js"; -import { MittwaldAPIV2 } from "@mittwald/api-client"; -import { projectFlags, withProjectId } from "../../../lib/project/flags.js"; -import { ExecRenderBaseCommand } from "../../../rendering/react/ExecRenderBaseCommand.js"; -import { - makeProcessRenderer, - processFlags, -} from "../../../rendering/react/process_flags.js"; -import { Success } from "../../../rendering/react/components/Success.js"; -import React from "react"; -import AppAppVersion = MittwaldAPIV2.Components.Schemas.AppAppVersion; -import { triggerAppInstallation } from "../../../lib/app/create.js"; - -export default class AppCreateTypo3 extends ExecRenderBaseCommand< - typeof AppCreateTypo3, - { appInstallationId: string } -> { - static appName: string = "Typo3"; - static appUuid: string = "352971cc-b96a-4a26-8651-b08d7c8a7357"; - static appNecessaryFlags: string[] = [ - "version", - "host", - "admin-user", - "admin-email", - "admin-pass", - "site-title", - ]; - - static description: string = `Creates new ${AppCreateTypo3.appName} Installation.`; - static flags = { - ...projectFlags, - ...processFlags, - version: Flags.string({ - required: true, - description: `Version of the ${AppCreateTypo3.appName} to be created - Defaults to latest`, - default: "latest", - }), - host: Flags.string({ - required: false, - description: `Host under which your ${AppCreateTypo3.appName} will be available (Needs to be created separately).`, - }), - "admin-user": Flags.string({ - required: false, - description: `First Admin User for your ${AppCreateTypo3.appName}.`, - }), - "admin-email": Flags.string({ - required: false, - description: "First Admin Users E-Mail.", - }), - "admin-pass": Flags.string({ - required: false, - description: "First Admin Users Password.", - }), - "site-title": Flags.string({ - required: false, - description: `Site Title of the created ${AppCreateTypo3.appName}.`, - }), - wait: Flags.boolean({ - char: "w", - description: `Wait for your ${AppCreateTypo3.appName} to be ready.`, - }), - }; - - protected async exec(): Promise<{ appInstallationId: string }> { - const process = makeProcessRenderer( - this.flags, - `Installing ${AppCreateTypo3.appName}`, - ); - let { flags, args } = await this.parse(AppCreateTypo3); - const projectId = await withProjectId( - this.apiClient, - flags, - args, - this.config, - ); - - flags = await autofillFlags( - this.apiClient, - process, - AppCreateTypo3.appNecessaryFlags, - flags, - projectId, - AppCreateTypo3.appName, - ); - - const appVersion: AppAppVersion = await normalizeToAppVersionUuid( - this.apiClient, - flags.version, - process, - AppCreateTypo3.appUuid, - ); - - const [appInstallationId, eventId] = await triggerAppInstallation( - this.apiClient, - process, - projectId, - flags, - appVersion, - ); - - let successText: string; - if (flags.wait) { - await waitUntilAppIsInstalled( - this.apiClient, - process, - appInstallationId, - eventId, - ); - successText = `Your ${AppCreateTypo3.appName} installation is now complete. Have fun! 🎉`; - } else { - successText = `Your ${AppCreateTypo3.appName} installation has started. Have fun when it's ready! 🎉`; - } - - process.complete({successText}); - return { appInstallationId }; - } - - protected render({ - appInstallationId, - }: { - appInstallationId: string; - }): React.ReactNode { - if (this.flags.quiet) { - this.log(appInstallationId); - } - return undefined; - } -} diff --git a/src/commands/app/install/wordpress.tsx b/src/commands/app/install/wordpress.tsx deleted file mode 100644 index 2444108f2..000000000 --- a/src/commands/app/install/wordpress.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import { Flags } from "@oclif/core"; -import { normalizeToAppVersionUuid } from "../../../lib/app/versions.js"; -import { autofillFlags } from "../../../lib/app/flags.js"; -import { waitUntilAppIsInstalled } from "../../../lib/app/wait.js"; -import { MittwaldAPIV2 } from "@mittwald/api-client"; -import { projectFlags, withProjectId } from "../../../lib/project/flags.js"; -import { ExecRenderBaseCommand } from "../../../rendering/react/ExecRenderBaseCommand.js"; -import { - makeProcessRenderer, - processFlags, -} from "../../../rendering/react/process_flags.js"; -import { Success } from "../../../rendering/react/components/Success.js"; -import React from "react"; -import AppAppVersion = MittwaldAPIV2.Components.Schemas.AppAppVersion; -import { triggerAppInstallation } from "../../../lib/app/create.js"; - -export default class AppCreateWordPress extends ExecRenderBaseCommand< - typeof AppCreateWordPress, - { appInstallationId: string } -> { - static appName: string = "WordPress"; - static appUuid: string = "da3aa3ae-4b6b-4398-a4a8-ee8def827876"; - static appNecessaryFlags: string[] = [ - "version", - "host", - "admin-user", - "admin-email", - "admin-pass", - "site-title", - ]; - - static description: string = `Creates new ${AppCreateWordPress.appName} Installation.`; - static flags = { - ...projectFlags, - ...processFlags, - version: Flags.string({ - required: true, - description: `Version of the ${AppCreateWordPress.appName} to be created - Defaults to latest`, - default: "latest", - }), - host: Flags.string({ - required: false, - description: `Host under which your ${AppCreateWordPress.appName} will be available (Needs to be created separately).`, - }), - "admin-user": Flags.string({ - required: false, - description: `First Admin User for your ${AppCreateWordPress.appName}.`, - }), - "admin-email": Flags.string({ - required: false, - description: "First Admin Users E-Mail.", - }), - "admin-pass": Flags.string({ - required: false, - description: "First Admin Users Password.", - }), - "site-title": Flags.string({ - required: false, - description: `Site Title of the created ${AppCreateWordPress.appName}.`, - }), - wait: Flags.boolean({ - char: "w", - description: `Wait for your ${AppCreateWordPress.appName} to be ready.`, - }), - }; - - protected async exec(): Promise<{ appInstallationId: string }> { - const process = makeProcessRenderer( - this.flags, - `Installing ${AppCreateWordPress.appName}`, - ); - let { flags, args } = await this.parse(AppCreateWordPress); - const projectId = await withProjectId( - this.apiClient, - flags, - args, - this.config, - ); - - flags = await autofillFlags( - this.apiClient, - process, - AppCreateWordPress.appNecessaryFlags, - flags, - projectId, - AppCreateWordPress.appName, - ); - - const appVersion: AppAppVersion = await normalizeToAppVersionUuid( - this.apiClient, - flags.version, - process, - AppCreateWordPress.appUuid, - ); - - const [appInstallationId, eventId] = await triggerAppInstallation( - this.apiClient, - process, - projectId, - flags, - appVersion, - ); - - let successText: string; - if (flags.wait) { - await waitUntilAppIsInstalled( - this.apiClient, - process, - appInstallationId, - eventId, - ); - successText = `Your ${AppCreateWordPress.appName} installation is now complete. Have fun! 🎉`; - } else { - successText = `Your ${AppCreateWordPress.appName} installation has started. Have fun when it's ready! 🎉`; - } - - process.complete({successText}); - return { appInstallationId }; - } - - protected render({ - appInstallationId, - }: { - appInstallationId: string; - }): React.ReactNode { - if (this.flags.quiet) { - this.log(appInstallationId); - } - return undefined; - } -} diff --git a/src/lib/app/flags.tsx b/src/lib/app/flags.tsx index ec29d4026..ed42297c5 100644 --- a/src/lib/app/flags.tsx +++ b/src/lib/app/flags.tsx @@ -2,20 +2,171 @@ import { getDefaultIngressForProject } from "../project/ingress.js"; import { Value } from "../../rendering/react/components/Value.js"; import { getProjectShortIdFromUuid } from "../project/shortId.js"; import { MittwaldAPIV2Client } from "@mittwald/api-client"; -import crypto from "crypto"; import React from "react"; import { Text } from "ink"; import { assertStatus } from "@mittwald/api-client-commons"; import { ProcessRenderer } from "../../rendering/react/process.js"; +import { projectFlags } from "../project/flags.js"; +import { + ProcessFlags, + processFlags, +} from "../../rendering/react/process_flags.js"; +import { Flags } from "@oclif/core"; +import { + BooleanFlag, + FlagInput, + OptionFlag, + OutputFlags, +} from "@oclif/core/lib/interfaces/parser.js"; +import { generatePasswordWithSpecialChars } from "../password.js"; + +export type AvailableFlagName = keyof AvailableFlags; + +interface AvailableFlags { + version: OptionFlag; + host: OptionFlag; + "admin-user": OptionFlag; + "admin-email": OptionFlag; + "admin-pass": OptionFlag; + "admin-firstname": OptionFlag; + "admin-lastname": OptionFlag; + "site-title": OptionFlag; + "shop-email": OptionFlag; + "shop-lang": OptionFlag; + "shop-currency": OptionFlag; + "install-mode": OptionFlag; + wait: BooleanFlag; +} + +function buildFlagsWithDescription(appName: string): AvailableFlags { + return { + version: Flags.string({ + required: true, + summary: `Version of the ${appName} to be installed.`, + description: `Specify the Version in wich your ${appName} will be installed. + If none is given the ${appName} will be installed in the latest available Version.`, + default: "latest", + }), + host: Flags.string({ + required: false, + summary: `Host to initially configure your ${appName} installation with; needs to be created separately.`, + description: `Specify a host which will be used during the installation and as an initial host for the ${appName} configuration. + If not given the default host for the given Project will be used. + This does not change the target of the used Host and can be changed later by configuring the Host and your ${appName} installation.`, + }), + "admin-user": Flags.string({ + required: false, + summary: "Username for your administrator-user.", + description: `Username of the first administrator-user which will be created during the ${appName} installation. + If not given an adequate username will be created from your mStudio Account Data. + After the installation is finished the Username can be changed and additional administrator-users can be created.`, + }), + "admin-email": Flags.string({ + required: false, + summary: "E-Mail-Address of your administrator-user.", + description: `E-Mail-Address that will correlate to the first administrator-user which will be created during the ${appName} installation. + If not given your mStudio Account-E-Mail-Address will be used. This E-Mail-Address can be changed after the installation is finished.`, + }), + "admin-pass": Flags.string({ + required: false, + summary: "Password of your administrator-user.", + description: `Password that will correlate to the first administrator-user which will be created during the ${appName} installation. + If not given a random secure Password will be generated and sent to stdout. This Password can be changed after the installation is finished`, + }), + "admin-firstname": Flags.string({ + required: false, + summary: "Firstname of your administrator-user.", + description: `Firstname that will correlate to the first administrator-user which will be created during the ${appName} installation. + If none is given your mStudio Account-Firstname will be used. This Firstname can be changed after the installation is finished`, + }), + "admin-lastname": Flags.string({ + required: false, + summary: "Lastname of your administrator-user.", + description: `Lastname that will correlate to the first administrator-user which will be created during the ${appName} installation. + If none is given your mStudio Account-Firstname will be used. This Lastname can be changed after the installation is finished`, + }), + "site-title": Flags.string({ + required: false, + summary: `Site Title for your ${appName} installation.`, + description: `Site Title which will be displayed in the Tab and at the top of the Frontend of your ${appName} installation. + It is also the Title shown in the App-Overview in the mStudio. + If none is given the Software Name and the given Project will be used. The Title can be changed after the installation is finished`, + }), + "shop-email": Flags.string({ + required: false, + summary: `E-Mail-Address your ${appName} will be working with.`, + description: `The E-Mail-Address your ${appName} shop will be using for correspondence.. + If not given your mStudio Account-E-Mail-Address will be used. This E-Mail-Address can be changed after the installation is finished.`, + }), + "shop-lang": Flags.string({ + required: false, + summary: `Language your ${appName} will be working with.`, + description: `The default Language your ${appName} shop will be using. + The Front- and Backend will be displayed using the given language. + If not given will default to German(de_DE). The language can be changed after the installation is finished.`, + }), + "shop-currency": Flags.string({ + required: false, + summary: `Currency your ${appName} will be working with.`, + description: `The default Currency your ${appName} shop communicates prices and calculates transactions with. + If not given will default to EUR(€). The currency can be changed after the installation is finished.`, + }), + "install-mode": Flags.string({ + required: true, + summary: `The installation variant your ${appName} will be installed with.`, + description: `${appName} can be installed in one of two different ways. your ${appName} shop communicates prices and calculates transactions with. + Either as a composer project or in a more manual fashion using the source directory and the ${appName} console install wizard. + If not given will default to composer installation. This can not be changed later.`, + options: ["composer", "symlink"], + default: "composer", + }), + wait: Flags.boolean({ + char: "w", + description: `Wait for your ${appName} to be ready.`, + }), + }; +} + +export type RelevantFlags = + ProcessFlags & Pick; +export type RelevantFlagInput = + FlagInput>; + +export function provideSupportedFlags< + TFlagNames extends readonly AvailableFlagName[], +>( + requestedFlagNames: TFlagNames, + appName: string, +): RelevantFlagInput { + const availableFlags: AvailableFlags = buildFlagsWithDescription(appName); + + const supportedFlags = requestedFlagNames.reduce( + (collector, currentValue) => { + return { + ...collector, + [currentValue]: availableFlags[currentValue], + }; + }, + {} as Pick, + ); + const flagsToReturn = { + ...projectFlags, + ...processFlags, + ...supportedFlags, + json: Flags.boolean({}), + }; + + return flagsToReturn as RelevantFlagInput; +} export async function autofillFlags( apiClient: MittwaldAPIV2Client, process: ProcessRenderer, - necessaryFlags: string[], - flags: any, + necessaryFlags: readonly AvailableFlagName[], + flags: Partial>>, projectId: string, appName: string, -) { +): Promise { const ownUser = await apiClient.user.getOwnAccount(); assertStatus(ownUser, 200); @@ -41,11 +192,6 @@ export async function autofillFlags( appName + " " + (await getProjectShortIdFromUuid(apiClient, projectId)); } - // Install Mode - if (necessaryFlags.includes("install-mode") && !flags["install-mode"]) { - flags["install-mode"] = "composer"; - } - // Admin User if (necessaryFlags.includes("admin-user") && !flags["admin-user"]) { if (ownUser.data.person) { @@ -67,20 +213,7 @@ export async function autofillFlags( // Admin Pass if (necessaryFlags.includes("admin-pass") && !flags["admin-pass"]) { - const specialCharsArray: string[] = ["%", "_", "-", "+", "&"]; - const passArray: string[] = crypto - .randomBytes(32) - .toString("base64") - .substring(0, 32) - .split(""); - - // replace random char in crypto-generated password with a random special char - passArray[Math.floor(Math.random() * (passArray.length - 1) + 1)] = - specialCharsArray[ - Math.floor(Math.random() * (specialCharsArray.length - 1)) - ]; - - flags["admin-pass"] = passArray.join(""); + flags["admin-pass"] = generatePasswordWithSpecialChars(); process.addInfo( @@ -91,8 +224,7 @@ export async function autofillFlags( // Admin Firstname if (necessaryFlags.includes("admin-firstname") && !flags["admin-firstname"]) { - // @ts-ignore - flags["admin-firstname"] = ownUser.data.person.firstName; + flags["admin-firstname"] = ownUser.data.person!.firstName; process.addInfo( Using mStudio firstname as Admin firstname ( @@ -103,8 +235,7 @@ export async function autofillFlags( // Admin Lastname if (necessaryFlags.includes("admin-lastname") && !flags["admin-lastname"]) { - // @ts-ignore - flags["admin-lastname"] = ownUser.data.person.lastName; + flags["admin-lastname"] = ownUser.data.person!.lastName; process.addInfo( Using mStudio firstname as Admin lastname ( @@ -135,8 +266,8 @@ export async function autofillFlags( } // Shop Language Code - if (necessaryFlags.includes("shop-language") && !flags["shop-language"]) { - flags["shop-language"] = "de-DE"; + if (necessaryFlags.includes("shop-lang") && !flags["shop-lang"]) { + flags["shop-lang"] = "de-DE"; process.addInfo(Using default shop language 'de_DE'.); } @@ -145,6 +276,4 @@ export async function autofillFlags( flags["shop-currency"] = "EUR"; process.addInfo(Using default shop currency '€'.); } - - return flags; } diff --git a/src/lib/app/create.ts b/src/lib/app/install.ts similarity index 54% rename from src/lib/app/create.ts rename to src/lib/app/install.ts index a5471916f..a245a5614 100644 --- a/src/lib/app/create.ts +++ b/src/lib/app/install.ts @@ -6,7 +6,7 @@ export async function triggerAppInstallation( apiClient: MittwaldAPIV2Client, process: ProcessRenderer, projectId: string, - flags: any, + flags: Record, appVersion: AppAppVersion, ) { const [appInstallationId, eventId] = await process.runStep( @@ -18,27 +18,10 @@ export async function triggerAppInstallation( appVersionId: appVersion.id, description: flags["site-title"], updatePolicy: "none", - userInputs: [ - { name: "host", value: flags.host }, - { name: "site_title", value: flags["site-title"] }, - { name: "admin_user", value: flags["admin-user"] }, - { name: "admin_email", value: flags["admin-email"] }, - { name: "admin_pass", value: flags["admin-pass"] }, - { - name: "admin_firstname", - value: flags["admin-firstname"], - }, - { - name: "admin_lastname", - value: flags["admin-lastname"], - }, - { name: "shop_email", value: flags["shop-email"] }, - { name: "shop_lang", value: flags["shop-language"] }, - { - name: "shop_currency", - value: flags["shop-currency"], - }, - ], + userInputs: Object.keys(flags).map((k) => ({ + name: k.replace("-", "_"), + value: flags[k], + })), }, }); diff --git a/src/lib/password.ts b/src/lib/password.ts new file mode 100644 index 000000000..e9d1e563f --- /dev/null +++ b/src/lib/password.ts @@ -0,0 +1,31 @@ +import crypto from "crypto"; + +const passwordAllowedSpecialChars: string[] = ["%", "_", "-", "+", "&"]; +const defaultPasswordLength = 32; +function randomInt(min: number, max: number): number { + return Math.floor(Math.random() * (max - 1) + min); +} + +function getRandomSpecialCharacter(): string { + return passwordAllowedSpecialChars[ + randomInt(0, passwordAllowedSpecialChars.length - 1) + ]; +} +export function generatePassword( + length: number = defaultPasswordLength, +): string { + return crypto.randomBytes(length).toString("base64").substring(0, length); +} +export function generatePasswordWithSpecialChars( + length: number = defaultPasswordLength, + amountSpecialChars: number = Math.floor(length / 8), +): string { + const passwordCharacters: string[] = generatePassword(length).split(""); + + for (let i = 0; i < amountSpecialChars; i++) { + passwordCharacters[randomInt(1, passwordCharacters.length - 1)] = + getRandomSpecialCharacter(); + } + + return passwordCharacters.join(""); +} diff --git a/src/rendering/react/process_flags.ts b/src/rendering/react/process_flags.ts index 40aac104a..6ca7c7af0 100644 --- a/src/rendering/react/process_flags.ts +++ b/src/rendering/react/process_flags.ts @@ -15,7 +15,6 @@ export const processFlags = { }; export type ProcessFlags = InferredFlags; - export const makeProcessRenderer = ( flags: { quiet: boolean }, title: string,