Bläddra i källkod

feat(create): Improve prompts for creating new app

Michael Bromley 2 år sedan
förälder
incheckning
d4d84fa76a
5 ändrade filer med 333 tillägg och 597 borttagningar
  1. 1 1
      .eslintrc.js
  2. 46 49
      packages/create/package.json
  3. 133 171
      packages/create/src/create-vendure-app.ts
  4. 115 98
      packages/create/src/gather-user-responses.ts
  5. 38 278
      yarn.lock

+ 1 - 1
.eslintrc.js

@@ -159,7 +159,7 @@ module.exports = {
         '@typescript-eslint/prefer-for-of': 'error',
         '@typescript-eslint/prefer-function-type': 'error',
         '@typescript-eslint/prefer-namespace-keyword': 'error',
-        '@typescript-eslint/quotes': ['error', 'single'],
+        '@typescript-eslint/quotes': 'off',
         '@typescript-eslint/require-await': 'warn',
         '@typescript-eslint/restrict-plus-operands': 'error',
         '@typescript-eslint/restrict-template-expressions': 'error',

+ 46 - 49
packages/create/package.json

@@ -1,51 +1,48 @@
 {
-    "name": "@vendure/create",
-    "version": "2.0.0-beta.3",
-    "license": "MIT",
-    "bin": {
-        "create": "./index.js"
-    },
-    "files": [
-        "index.js",
-        "lib/**/*",
-        "assets/**/*"
-    ],
-    "scripts": {
-        "dev": "ts-node src/create-vendure-app.ts",
-        "copy-assets": "rimraf assets && ts-node ./build.ts",
-        "build": "yarn copy-assets && rimraf lib && tsc -p ./tsconfig.build.json",
-        "watch": "yarn copy-assets && rimraf lib && tsc -p ./tsconfig.build.json -w",
-        "lint": "eslint --fix ."
-    },
-    "homepage": "https://www.vendure.io/",
-    "funding": "https://github.com/sponsors/michaelbromley",
-    "publishConfig": {
-        "access": "public"
-    },
-    "devDependencies": {
-        "@types/cross-spawn": "^6.0.2",
-        "@types/detect-port": "^1.3.0",
-        "@types/fs-extra": "^9.0.1",
-        "@types/handlebars": "^4.1.0",
-        "@types/listr": "^0.14.2",
-        "@types/semver": "^6.2.2",
-        "@vendure/core": "2.0.0-beta.3",
-        "rimraf": "^3.0.2",
-        "ts-node": "^10.9.1",
-        "typescript": "4.9.5"
-    },
-    "dependencies": {
-        "@vendure/common": "2.0.0-beta.3",
-        "commander": "^10.0.0",
-        "cross-spawn": "^7.0.3",
-        "detect-port": "^1.5.1",
-        "fs-extra": "^11.1.1",
-        "handlebars": "^4.7.6",
-        "listr": "^0.14.3",
-        "picocolors": "^1.0.0",
-        "prompts": "^2.3.2",
-        "rxjs": "^7.5.4",
-        "semver": "^7.3.2",
-        "tcp-port-used": "^1.0.1"
-    }
+  "name": "@vendure/create",
+  "version": "2.0.0-beta.3",
+  "license": "MIT",
+  "bin": {
+    "create": "./index.js"
+  },
+  "files": [
+    "index.js",
+    "lib/**/*",
+    "assets/**/*"
+  ],
+  "scripts": {
+    "dev": "ts-node src/create-vendure-app.ts",
+    "copy-assets": "rimraf assets && ts-node ./build.ts",
+    "build": "yarn copy-assets && rimraf lib && tsc -p ./tsconfig.build.json",
+    "watch": "yarn copy-assets && rimraf lib && tsc -p ./tsconfig.build.json -w",
+    "lint": "eslint --fix ."
+  },
+  "homepage": "https://www.vendure.io/",
+  "funding": "https://github.com/sponsors/michaelbromley",
+  "publishConfig": {
+    "access": "public"
+  },
+  "devDependencies": {
+    "@types/cross-spawn": "^6.0.2",
+    "@types/detect-port": "^1.3.0",
+    "@types/fs-extra": "^9.0.1",
+    "@types/handlebars": "^4.1.0",
+    "@types/semver": "^6.2.2",
+    "@vendure/core": "^2.0.0-beta.3",
+    "rimraf": "^3.0.2",
+    "ts-node": "^10.9.1",
+    "typescript": "4.9.5"
+  },
+  "dependencies": {
+    "@clack/prompts": "^0.6.3",
+    "@vendure/common": "^2.0.0-beta.3",
+    "commander": "^10.0.0",
+    "cross-spawn": "^7.0.3",
+    "detect-port": "^1.5.1",
+    "fs-extra": "^11.1.1",
+    "handlebars": "^4.7.6",
+    "picocolors": "^1.0.0",
+    "semver": "^7.3.2",
+    "tcp-port-used": "^1.0.1"
+  }
 }

+ 133 - 171
packages/create/src/create-vendure-app.ts

@@ -1,12 +1,11 @@
 /* eslint-disable no-console */
+import { intro, note, outro, spinner } from '@clack/prompts';
 import { program } from 'commander';
 import detectPort from 'detect-port';
 import fs from 'fs-extra';
-import Listr from 'listr';
 import os from 'os';
 import path from 'path';
 import pc from 'picocolors';
-import { Observable } from 'rxjs';
 
 import { REQUIRED_NODE_VERSION, SERVER_PORT } from './constants';
 import { gatherCiUserResponses, gatherUserResponses } from './gather-user-responses';
@@ -69,10 +68,9 @@ export async function createVendureApp(
         process.exit(1);
     }
 
-    console.log(pc.cyan(`Welcome to @vendure/create v${packageJson.version as string}!`));
-    console.log();
-    console.log("Let's configure a new Vendure project. First a few questions:");
-    console.log();
+    intro(
+        `Let's create a ${pc.blue(pc.bold('Vendure App'))} ✨ ${pc.dim(`v${packageJson.version as string}`)}`,
+    );
 
     const root = path.resolve(name);
     const appName = path.basename(root);
@@ -80,7 +78,7 @@ export async function createVendureApp(
     const useYarn = useNpm ? false : shouldUseYarn();
     if (scaffoldExists) {
         console.log(
-            pc.green(
+            pc.yellow(
                 'It appears that a new Vendure project scaffold already exists. Re-using the existing files...',
             ),
         );
@@ -125,187 +123,151 @@ export async function createVendureApp(
         },
     };
 
-    console.log();
-    console.log(`Setting up your new Vendure project in ${pc.green(root)}`);
-    console.log('This may take a few minutes...');
-    console.log();
+    const setupSpinner = spinner();
+    setupSpinner.start(
+        `Setting up your new Vendure project in ${pc.green(root)}\nThis may take a few minutes...`,
+    );
 
     const rootPathScript = (fileName: string): string => path.join(root, `${fileName}.ts`);
     const srcPathScript = (fileName: string): string => path.join(root, 'src', `${fileName}.ts`);
 
-    const listrTasks: Listr.ListrTask[] = [];
-    if (scaffoldExists) {
-        // ...
-    } else {
-        listrTasks.push(
-            {
-                title: 'Installing dependencies',
-                task: (() => {
-                    return new Observable(subscriber => {
-                        subscriber.next('Creating package.json');
-                        fs.writeFileSync(
-                            path.join(root, 'package.json'),
-                            JSON.stringify(packageJsonContents, null, 2) + os.EOL,
-                        );
-                        const { dependencies, devDependencies } = getDependencies(
-                            dbType,
-                            `@${packageJson.version as string}`,
-                        );
+    fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify(packageJsonContents, null, 2) + os.EOL);
+    const { dependencies, devDependencies } = getDependencies(dbType, `@${packageJson.version as string}`);
+    setupSpinner.stop(`Created ${pc.green('package.json')}`);
 
-                        subscriber.next(`Installing ${dependencies.join(', ')}`);
-                        installPackages(root, useYarn, dependencies, false, logLevel, isCi)
-                            .then(() => {
-                                if (devDependencies.length) {
-                                    subscriber.next(`Installing ${devDependencies.join(', ')}`);
-                                    return installPackages(
-                                        root,
-                                        useYarn,
-                                        devDependencies,
-                                        true,
-                                        logLevel,
-                                        isCi,
-                                    );
-                                }
-                            })
-                            .then(() => subscriber.complete())
-                            .catch(err => subscriber.error(err));
-                    });
-                }) as any,
-            },
-            {
-                title: 'Generating app scaffold',
-                task: ctx => {
-                    return new Observable(subscriber => {
-                        fs.ensureDirSync(path.join(root, 'src'));
-                        const assetPath = (fileName: string) => path.join(__dirname, '../assets', fileName);
-                        ctx.configFile = srcPathScript('vendure-config');
+    const installSpinner = spinner();
+    installSpinner.start(`Installing ${dependencies[0]} + ${dependencies.length - 1} more dependencies`);
+    try {
+        await installPackages(root, useYarn, dependencies, false, logLevel, isCi);
+    } catch (e) {
+        outro(pc.red(`Failed to install dependencies. Please try again.`));
+        process.exit(1);
+    }
+    installSpinner.stop(`Successfully installed ${dependencies.length} dependencies`);
 
-                        fs.writeFile(ctx.configFile, configSource)
-                            .then(() => fs.writeFile(path.join(root, '.env'), envSource))
-                            .then(() => fs.writeFile(srcPathScript('environment.d'), envDtsSource))
-                            .then(() => fs.writeFile(srcPathScript('index'), indexSource))
-                            .then(() => fs.writeFile(srcPathScript('index-worker'), indexWorkerSource))
-                            .then(() => fs.writeFile(rootPathScript('migration'), migrationSource))
-                            .then(() => fs.writeFile(path.join(root, 'README.md'), readmeSource))
-                            .then(() => fs.writeFile(path.join(root, 'Dockerfile'), dockerfileSource))
-                            .then(() =>
-                                fs.writeFile(path.join(root, 'docker-compose.yml'), dockerComposeSource),
-                            )
-                            .then(() => fs.mkdir(path.join(root, 'src/plugins')))
-                            .then(() =>
-                                fs.copyFile(assetPath('gitignore.template'), path.join(root, '.gitignore')),
-                            )
-                            .then(() => {
-                                subscriber.next('Created files');
-                                return fs.copyFile(
-                                    assetPath('tsconfig.template.json'),
-                                    path.join(root, 'tsconfig.json'),
-                                );
-                            })
-                            .then(() => createDirectoryStructure(root))
-                            .then(() => {
-                                subscriber.next('Created directory structure');
-                                return copyEmailTemplates(root);
-                            })
-                            .then(() => {
-                                subscriber.next('Copied email templates');
-                                subscriber.complete();
-                            })
-                            .catch(err => subscriber.error(err));
-                    }) as any;
-                },
-            },
+    if (devDependencies.length) {
+        const installDevSpinner = spinner();
+        installDevSpinner.start(
+            `Installing ${devDependencies[0]} + ${devDependencies.length - 1} more dev dependencies`,
         );
+        try {
+            await installPackages(root, useYarn, devDependencies, true, logLevel, isCi);
+        } catch (e) {
+            outro(pc.red(`Failed to install dev dependencies. Please try again.`));
+            process.exit(1);
+        }
+        installDevSpinner.stop(`Successfully installed ${devDependencies.length} dev dependencies`);
     }
-    listrTasks.push({
-        title: 'Initializing server',
-        task: async ctx => {
-            try {
-                // register ts-node so that the config file can be loaded
-                // eslint-disable-next-line @typescript-eslint/no-var-requires
-                require(path.join(root, 'node_modules/ts-node')).register();
-                const { populate } = await import(path.join(root, 'node_modules/@vendure/core/cli/populate'));
-                const { bootstrap, DefaultLogger, LogLevel, JobQueueService } = await import(
-                    path.join(root, 'node_modules/@vendure/core/dist/index')
-                );
-                const configFile = srcPathScript('vendure-config');
-                const { config } = await import(configFile);
-                const assetsDir = path.join(__dirname, '../assets');
 
-                const initialDataPath = path.join(assetsDir, 'initial-data.json');
-                const port = await detectPort(3000);
-                const vendureLogLevel =
-                    logLevel === 'silent'
-                        ? LogLevel.Error
-                        : logLevel === 'verbose'
-                        ? LogLevel.Verbose
-                        : LogLevel.Info;
+    const scaffoldSpinner = spinner();
+    scaffoldSpinner.start(`Generating app scaffold`);
+    fs.ensureDirSync(path.join(root, 'src'));
+    const assetPath = (fileName: string) => path.join(__dirname, '../assets', fileName);
+    const configFile = srcPathScript('vendure-config');
 
-                const bootstrapFn = async () => {
-                    await checkDbConnection(config.dbConnectionOptions, root);
-                    const _app = await bootstrap({
-                        ...config,
-                        apiOptions: {
-                            ...(config.apiOptions ?? {}),
-                            port,
-                        },
-                        silent: logLevel === 'silent',
-                        dbConnectionOptions: {
-                            ...config.dbConnectionOptions,
-                            synchronize: true,
-                        },
-                        logger: new DefaultLogger({ level: vendureLogLevel }),
-                        importExportOptions: {
-                            importAssetsDir: path.join(assetsDir, 'images'),
-                        },
-                    });
-                    await _app.get(JobQueueService).start();
-                    return _app;
-                };
+    try {
+        await fs
+            .writeFile(configFile, configSource)
+            .then(() => fs.writeFile(path.join(root, '.env'), envSource))
+            .then(() => fs.writeFile(srcPathScript('environment.d'), envDtsSource))
+            .then(() => fs.writeFile(srcPathScript('index'), indexSource))
+            .then(() => fs.writeFile(srcPathScript('index-worker'), indexWorkerSource))
+            .then(() => fs.writeFile(rootPathScript('migration'), migrationSource))
+            .then(() => fs.writeFile(path.join(root, 'README.md'), readmeSource))
+            .then(() => fs.writeFile(path.join(root, 'Dockerfile'), dockerfileSource))
+            .then(() => fs.writeFile(path.join(root, 'docker-compose.yml'), dockerComposeSource))
+            .then(() => fs.mkdir(path.join(root, 'src/plugins')))
+            .then(() => fs.copyFile(assetPath('gitignore.template'), path.join(root, '.gitignore')))
+            .then(() => fs.copyFile(assetPath('tsconfig.template.json'), path.join(root, 'tsconfig.json')))
+            .then(() => createDirectoryStructure(root))
+            .then(() => copyEmailTemplates(root));
+    } catch (e) {
+        outro(pc.red(`Failed to create app scaffold. Please try again.`));
+        process.exit(1);
+    }
+    scaffoldSpinner.stop(`Generated app scaffold`);
 
-                const app = await populate(
-                    bootstrapFn,
-                    initialDataPath,
-                    populateProducts ? path.join(assetsDir, 'products.csv') : undefined,
-                );
+    const populateSpinner = spinner();
+    populateSpinner.start(`Initializing your new Vendure server`);
+    // register ts-node so that the config file can be loaded
+    // eslint-disable-next-line @typescript-eslint/no-var-requires
+    require(path.join(root, 'node_modules/ts-node')).register();
 
-                // Pause to ensure the worker jobs have time to complete.
-                if (isCi) {
-                    console.log('[CI] Pausing before close...');
-                }
-                await new Promise(resolve => setTimeout(resolve, isCi ? 30000 : 2000));
-                await app.close();
-                if (isCi) {
-                    console.log('[CI] Pausing after close...');
-                    await new Promise(resolve => setTimeout(resolve, 10000));
-                }
-            } catch (e: any) {
-                console.log();
-                console.error(pc.red(e.message));
-                console.log();
-                console.log(e);
-                throw e;
-            }
-        },
-    });
+    try {
+        const { populate } = await import(path.join(root, 'node_modules/@vendure/core/cli/populate'));
+        const { bootstrap, DefaultLogger, LogLevel, JobQueueService } = await import(
+            path.join(root, 'node_modules/@vendure/core/dist/index')
+        );
+        const { config } = await import(configFile);
+        const assetsDir = path.join(__dirname, '../assets');
 
-    const tasks = new Listr(listrTasks);
+        const initialDataPath = path.join(assetsDir, 'initial-data.json');
+        const port = await detectPort(3000);
+        const vendureLogLevel =
+            logLevel === 'silent'
+                ? LogLevel.Error
+                : logLevel === 'verbose'
+                ? LogLevel.Verbose
+                : LogLevel.Info;
 
-    try {
-        await tasks.run();
-    } catch (e: any) {
+        const bootstrapFn = async () => {
+            await checkDbConnection(config.dbConnectionOptions, root);
+            const _app = await bootstrap({
+                ...config,
+                apiOptions: {
+                    ...(config.apiOptions ?? {}),
+                    port,
+                },
+                silent: logLevel === 'silent',
+                dbConnectionOptions: {
+                    ...config.dbConnectionOptions,
+                    synchronize: true,
+                },
+                logger: new DefaultLogger({ level: vendureLogLevel }),
+                importExportOptions: {
+                    importAssetsDir: path.join(assetsDir, 'images'),
+                },
+            });
+            await _app.get(JobQueueService).start();
+            return _app;
+        };
+
+        const app = await populate(
+            bootstrapFn,
+            initialDataPath,
+            populateProducts ? path.join(assetsDir, 'products.csv') : undefined,
+        );
+
+        // Pause to ensure the worker jobs have time to complete.
+        if (isCi) {
+            console.log('[CI] Pausing before close...');
+        }
+        await new Promise(resolve => setTimeout(resolve, isCi ? 30000 : 2000));
+        await app.close();
+        if (isCi) {
+            console.log('[CI] Pausing after close...');
+            await new Promise(resolve => setTimeout(resolve, 10000));
+        }
+    } catch (e) {
+        console.log(e);
+        outro(pc.red(`Failed to initialize server. Please try again.`));
         process.exit(1);
     }
+    populateSpinner.stop(`Server successfully initialized${populateProducts ? ' and populated' : ''}`);
+
     const startCommand = useYarn ? 'yarn dev' : 'npm run dev';
-    console.log();
-    console.log(pc.green(`Success! Created a new Vendure server at ${root}`));
-    console.log();
-    console.log('We suggest that you start by typing:');
-    console.log();
-    console.log(pc.green(`    cd ${name}`));
-    console.log(pc.green(`    ${startCommand}`));
-    console.log();
-    console.log('Happy hacking!');
+    const nextSteps = [
+        `${pc.green('Success!')} Created a new Vendure server at:`,
+        `\n`,
+        pc.italic(root),
+        `\n`,
+        `We suggest that you start by typing:`,
+        `\n`,
+        pc.gray('$ ') + pc.blue(pc.bold(`cd ${name}`)),
+        pc.gray('$ ') + pc.blue(pc.bold(`${startCommand}`)),
+    ];
+    note(nextSteps.join('\n'));
+    outro(`Happy hacking!`);
     process.exit(0);
 }
 

+ 115 - 98
packages/create/src/gather-user-responses.ts

@@ -1,11 +1,24 @@
+import { cancel, intro, isCancel, outro, select, text } from '@clack/prompts';
 import { SUPER_ADMIN_USER_IDENTIFIER, SUPER_ADMIN_USER_PASSWORD } from '@vendure/common/lib/shared-constants';
 import fs from 'fs-extra';
 import Handlebars from 'handlebars';
 import path from 'path';
-import prompts, { PromptObject } from 'prompts';
 
 import { DbType, FileSources, UserResponses } from './types';
 
+interface PromptAnswers {
+    dbType: DbType;
+    dbHost: string | symbol;
+    dbPort: string | symbol;
+    dbName: string | symbol;
+    dbSchema?: string | symbol;
+    dbUserName: string | symbol;
+    dbPassword: string | symbol;
+    superadminIdentifier: string | symbol;
+    superadminPassword: string | symbol;
+    populateProducts: boolean | symbol;
+}
+
 /* eslint-disable no-console */
 
 /**
@@ -16,106 +29,101 @@ export async function gatherUserResponses(
     alreadyRanScaffold: boolean,
     useYarn: boolean,
 ): Promise<UserResponses> {
-    function onSubmit(prompt: PromptObject, answer: any) {
-        if (prompt.name === 'dbType') {
-            dbType = answer;
-        }
-    }
+    const dbType = (await select({
+        message: 'Which database are you using?',
+        options: [
+            { label: 'MySQL', value: 'mysql' },
+            { label: 'MariaDB', value: 'mariadb' },
+            { label: 'Postgres', value: 'postgres' },
+            { label: 'SQLite', value: 'sqlite' },
+            { label: 'SQL.js', value: 'sqljs' },
+        ],
+        initialValue: 'sqlite' as DbType,
+    })) as DbType;
+    checkCancel(dbType);
 
-    let dbType: DbType;
-
-    const scaffoldPrompts: Array<prompts.PromptObject<any>> = [
-        {
-            type: 'select',
-            name: 'dbType',
-            message: 'Which database are you using?',
-            choices: [
-                { title: 'MySQL', value: 'mysql' },
-                { title: 'MariaDB', value: 'mariadb' },
-                { title: 'Postgres', value: 'postgres' },
-                { title: 'SQLite', value: 'sqlite' },
-                { title: 'SQL.js', value: 'sqljs' },
-                // Don't show these until they have been tested.
-                // { title: 'MS SQL Server', value: 'mssql' },
-                // { title: 'Oracle', value: 'oracle' },
-            ],
-            initial: 0 as any,
-        },
-        {
-            type: (() => (dbType === 'sqlite' || dbType === 'sqljs' ? null : 'text')) as any,
-            name: 'dbHost',
-            message: 'What\'s the database host address?',
-            initial: 'localhost',
-        },
-        {
-            type: (() => (dbType === 'sqlite' || dbType === 'sqljs' ? null : 'text')) as any,
-            name: 'dbPort',
-            message: 'What port is the database listening on?',
-            initial: (() => defaultDBPort(dbType)) as any,
-        },
-        {
-            type: (() => (dbType === 'sqlite' || dbType === 'sqljs' ? null : 'text')) as any,
-            name: 'dbName',
-            message: 'What\'s the name of the database?',
-            initial: 'vendure',
-        },
-        {
-            type: (() => (dbType === 'postgres' ? 'text' : null)) as any,
-            name: 'dbSchema',
-            message: 'What\'s the schema name we should use?',
-            initial: 'public',
-        },
-        {
-            type: (() => (dbType === 'sqlite' || dbType === 'sqljs' ? null : 'text')) as any,
-            name: 'dbUserName',
-            message: 'What\'s the database user name?',
-            initial: 'root',
-        },
-        {
-            type: (() => (dbType === 'sqlite' || dbType === 'sqljs' ? null : 'password')) as any,
-            name: 'dbPassword',
-            message: 'What\'s the database password?',
-        },
-        {
-            type: 'text',
-            name: 'superadminIdentifier',
-            message: 'What identifier do you want to use for the superadmin user?',
-            initial: SUPER_ADMIN_USER_IDENTIFIER,
-        },
-        {
-            type: 'text',
-            name: 'superadminPassword',
-            message: 'What password do you want to use for the superadmin user?',
-            initial: SUPER_ADMIN_USER_PASSWORD,
-        },
-    ];
-
-    const initPrompts: Array<prompts.PromptObject<any>> = [
-        {
-            type: 'toggle',
-            name: 'populateProducts',
-            message: 'Populate with some sample product data?',
-            initial: true,
-            active: 'yes',
-            inactive: 'no',
-        },
-    ];
-
-    const answers = await prompts(alreadyRanScaffold ? initPrompts : [...scaffoldPrompts, ...initPrompts], {
-        onSubmit,
-        onCancel() {
-            /* */
-            console.log('Setup cancelled');
-            process.exit(1);
-        },
+    const hasConnection = dbType !== 'sqlite' && dbType !== 'sqljs';
+    const dbHost = hasConnection
+        ? await text({
+              message: "What's the database host address?",
+              initialValue: 'localhost',
+          })
+        : '';
+    checkCancel(dbHost);
+    const dbPort = hasConnection
+        ? await text({
+              message: 'What port is the database listening on?',
+              initialValue: defaultDBPort(dbType).toString(),
+          })
+        : '';
+    checkCancel(dbPort);
+    const dbName = hasConnection
+        ? await text({
+              message: "What's the name of the database?",
+              initialValue: 'vendure',
+          })
+        : '';
+    checkCancel(dbName);
+    const dbSchema =
+        dbType === 'postgres'
+            ? await text({
+                  message: "What's the schema name we should use?",
+                  initialValue: 'public',
+              })
+            : '';
+    checkCancel(dbSchema);
+    const dbUserName = hasConnection
+        ? await text({
+              message: "What's the database user name?",
+          })
+        : '';
+    checkCancel(dbUserName);
+    const dbPassword = hasConnection
+        ? await text({
+              message: "What's the database password?",
+              defaultValue: '',
+          })
+        : '';
+    checkCancel(dbPassword);
+    const superadminIdentifier = await text({
+        message: 'What identifier do you want to use for the superadmin user?',
+        initialValue: SUPER_ADMIN_USER_IDENTIFIER,
     });
+    checkCancel(superadminIdentifier);
+    const superadminPassword = await text({
+        message: 'What password do you want to use for the superadmin user?',
+        initialValue: SUPER_ADMIN_USER_PASSWORD,
+    });
+    checkCancel(superadminPassword);
+    const populateProducts = await select({
+        message: 'Populate with some sample product data?',
+        options: [
+            { label: 'yes', value: true },
+            { label: 'no', value: false },
+        ],
+        initialValue: true,
+    });
+    checkCancel(populateProducts);
+
+    const answers: PromptAnswers = {
+        dbType,
+        dbHost,
+        dbPort,
+        dbName,
+        dbSchema,
+        dbUserName,
+        dbPassword,
+        superadminIdentifier,
+        superadminPassword,
+        populateProducts,
+    };
 
     return {
         ...(await generateSources(root, answers, useYarn)),
-        dbType: answers.dbType,
-        populateProducts: answers.populateProducts,
-        superadminIdentifier: answers.superadminIdentifier,
-        superadminPassword: answers.superadminPassword,
+        dbType,
+        populateProducts: answers.populateProducts as boolean,
+        superadminIdentifier: answers.superadminIdentifier as string,
+        superadminPassword: answers.superadminPassword as string,
     };
 }
 
@@ -144,10 +152,18 @@ export async function gatherCiUserResponses(root: string, useYarn: boolean): Pro
     };
 }
 
+function checkCancel<T>(value: T | symbol): value is T {
+    if (isCancel(value)) {
+        cancel('Setup cancelled.');
+        process.exit(0);
+    }
+    return true;
+}
+
 /**
  * Create the server index, worker and config source code based on the options specified by the CLI prompts.
  */
-async function generateSources(root: string, answers: any, useYarn: boolean): Promise<FileSources> {
+async function generateSources(root: string, answers: PromptAnswers, useYarn: boolean): Promise<FileSources> {
     const assetPath = (fileName: string) => path.join(__dirname, '../assets', fileName);
 
     /**
@@ -156,7 +172,7 @@ async function generateSources(root: string, answers: any, useYarn: boolean): Pr
      * Instead, we disable escaping and use this custom helper to escape only the single quote character.
      */
     Handlebars.registerHelper('escapeSingle', (aString: unknown) => {
-        return typeof aString === 'string' ? aString.replace(/'/g, '\\\'') : aString;
+        return typeof aString === 'string' ? aString.replace(/'/g, "\\'") : aString;
     });
 
     const templateContext = {
@@ -174,6 +190,7 @@ async function generateSources(root: string, answers: any, useYarn: boolean): Pr
         const template = await fs.readFile(assetPath(filename), 'utf-8');
         return Handlebars.compile(template, { noEscape })(templateContext);
     }
+
     return {
         indexSource: await createSourceFile('index.hbs'),
         indexWorkerSource: await createSourceFile('index-worker.hbs'),

+ 38 - 278
yarn.lock

@@ -2580,6 +2580,23 @@
     "@cds/city" "^1.1.0"
     modern-normalize "1.1.0"
 
+"@clack/core@^0.3.2":
+  version "0.3.2"
+  resolved "https://registry.yarnpkg.com/@clack/core/-/core-0.3.2.tgz#23c6e440ed263c2f012b1cc0806386481e78420b"
+  integrity sha512-FZnsNynwGDIDktx6PEZK1EuCkFpY4ldEX6VYvfl0dqeoLPb9Jpw1xoUXaVcGR8ExmYNm1w2vdGdJkEUYD/2pqg==
+  dependencies:
+    picocolors "^1.0.0"
+    sisteransi "^1.0.5"
+
+"@clack/prompts@^0.6.3":
+  version "0.6.3"
+  resolved "https://registry.yarnpkg.com/@clack/prompts/-/prompts-0.6.3.tgz#c6184bb3f8685dfc72207d2ff051f7a6a733fcb3"
+  integrity sha512-AM+kFmAHawpUQv2q9+mcB6jLKxXGjgu/r2EQjEwujgpCdzrST6BJqYw00GRn56/L/Izw5U7ImoLmy00X/r80Pw==
+  dependencies:
+    "@clack/core" "^0.3.2"
+    picocolors "^1.0.0"
+    sisteransi "^1.0.5"
+
 "@clr/angular@^15.4.0":
   version "15.4.0"
   resolved "https://registry.yarnpkg.com/@clr/angular/-/angular-15.4.0.tgz#e2f311fcd71153b1c521623f45c654fe6e99717f"
@@ -4688,13 +4705,6 @@
     estree-walker "^2.0.2"
     picomatch "^2.3.1"
 
-"@samverschueren/stream-to-observable@^0.3.0":
-  version "0.3.1"
-  resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.1.tgz#a21117b19ee9be70c379ec1877537ef2e1c63301"
-  integrity sha512-c/qwwcHyafOQuVQJj0IlBjf5yYgBI7YPJ77k4fOJYesb41jio65eaJODRUmfYKhTOFBrIZ66kgvGPlNbjuoRdQ==
-  dependencies:
-    any-observable "^0.3.0"
-
 "@schematics/angular@16.0.3":
   version "16.0.3"
   resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-16.0.3.tgz#adec0e5cdb5280125a8d30a562356d5766d47d64"
@@ -5207,14 +5217,6 @@
     "@types/koa-compose" "*"
     "@types/node" "*"
 
-"@types/listr@^0.14.2":
-  version "0.14.4"
-  resolved "https://registry.yarnpkg.com/@types/listr/-/listr-0.14.4.tgz#6ba2a4206615cf80d79d10f9ba9701c303cd57b6"
-  integrity sha512-+MWvidNujBUgJsi4yMVwEQQwaHe6oHedPSy+dwk3akGEeuIbvhWkK+TGsXSwbFup7Y0cCBb+wzzdD+yGKp7sOg==
-  dependencies:
-    "@types/node" "*"
-    rxjs "^6.5.1"
-
 "@types/localtunnel@2.0.1":
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/@types/localtunnel/-/localtunnel-2.0.1.tgz#c28a067de1da81b0b4730a9fc2f98e067f973996"
@@ -6125,11 +6127,6 @@ ansi-colors@^1.0.1:
   dependencies:
     ansi-wrap "^0.1.0"
 
-ansi-escapes@^3.0.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b"
-  integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==
-
 ansi-escapes@^4.2.1, ansi-escapes@^4.3.0:
   version "4.3.2"
   resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e"
@@ -6181,11 +6178,6 @@ ansi-regex@^6.0.1:
   resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a"
   integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==
 
-ansi-styles@^2.2.1:
-  version "2.2.1"
-  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
-  integrity sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==
-
 ansi-styles@^3.2.0, ansi-styles@^3.2.1:
   version "3.2.1"
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
@@ -6215,11 +6207,6 @@ ansi-wrap@0.1.0, ansi-wrap@^0.1.0:
   resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf"
   integrity sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw==
 
-any-observable@^0.3.0:
-  version "0.3.0"
-  resolved "https://registry.yarnpkg.com/any-observable/-/any-observable-0.3.0.tgz#af933475e5806a67d0d7df090dd5e8bef65d119b"
-  integrity sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==
-
 any-promise@^1.0.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
@@ -7329,18 +7316,7 @@ chalk@4.1.0:
     ansi-styles "^4.1.0"
     supports-color "^7.1.0"
 
-chalk@^1.0.0, chalk@^1.1.3:
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
-  integrity sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==
-  dependencies:
-    ansi-styles "^2.2.1"
-    escape-string-regexp "^1.0.2"
-    has-ansi "^2.0.0"
-    strip-ansi "^3.0.0"
-    supports-color "^2.0.0"
-
-chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2:
+chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.2:
   version "2.4.2"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
   integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
@@ -7536,13 +7512,6 @@ cli-cursor@3.1.0, cli-cursor@^3.1.0:
   dependencies:
     restore-cursor "^3.1.0"
 
-cli-cursor@^2.0.0, cli-cursor@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5"
-  integrity sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==
-  dependencies:
-    restore-cursor "^2.0.0"
-
 cli-highlight@^2.1.11:
   version "2.1.11"
   resolved "https://registry.yarnpkg.com/cli-highlight/-/cli-highlight-2.1.11.tgz#49736fa452f0aaf4fae580e30acb26828d2dc1bf"
@@ -7565,14 +7534,6 @@ cli-spinners@^2.5.0:
   resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.8.0.tgz#e97a3e2bd00e6d85aa0c13d7f9e3ce236f7787fc"
   integrity sha512-/eG5sJcvEIwxcdYM86k5tPwn0MUzkX5YY3eImTGpJOZgVe4SdTMY14vQpcxgBzJ0wXwAYrS8E+c3uHeK4JNyzQ==
 
-cli-truncate@^0.2.1:
-  version "0.2.1"
-  resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-0.2.1.tgz#9f15cfbb0705005369216c626ac7d05ab90dd574"
-  integrity sha512-f4r4yJnbT++qUPI9NR4XLDLq41gQ+uqnPItWG0F5ZkehuNiTTa3EY0S4AqTSUOeJ7/zU41oWPQSNkW5BqPL9bg==
-  dependencies:
-    slice-ansi "0.0.4"
-    string-width "^1.0.1"
-
 cli-truncate@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7"
@@ -8408,11 +8369,6 @@ dataloader@2.2.2, dataloader@^2.2.2:
   resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-2.2.2.tgz#216dc509b5abe39d43a9b9d97e6e5e473dfbe3e0"
   integrity sha512-8YnDaaf7N3k/q5HnTJVuzSyLETjoZjVmHc4AeKAzOvKHEFQKcn64OKBfzHYtE9zGjctNM7V9I0MfnUVLpi7M5g==
 
-date-fns@^1.27.2:
-  version "1.30.1"
-  resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c"
-  integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==
-
 date-fns@^2.0.1, date-fns@^2.16.1, date-fns@^2.28.0:
   version "2.29.3"
   resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8"
@@ -8948,11 +8904,6 @@ electron-to-chromium@^1.4.284:
   resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.376.tgz#a771d6c4db028634df126348a4f94075e07e7f65"
   integrity sha512-TFeOKd98TpJzRHkr4Aorn16QkMnuCQuGAE6IZ0wYF+qkbSfMPqjplvRppR02tMUpVxZz8nyBNvVm9lIZsqrbPQ==
 
-elegant-spinner@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e"
-  integrity sha512-B+ZM+RXvRqQaAmkMlO/oSe5nMUOaUnyfGYCEHoR8wrXsZR2mA0XVibsxV1bvTwxdRWah1PkQqso2EzhILGHtEQ==
-
 emoji-regex@^7.0.1:
   version "7.0.3"
   resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
@@ -9241,7 +9192,7 @@ escape-html@~1.0.3:
   resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
   integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
 
-escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
+escape-string-regexp@^1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
   integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==
@@ -9780,21 +9731,6 @@ figures@3.2.0, figures@^3.0.0:
   dependencies:
     escape-string-regexp "^1.0.5"
 
-figures@^1.7.0:
-  version "1.7.0"
-  resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e"
-  integrity sha512-UxKlfCRuCBxSXU4C6t9scbDyWZ4VlaFFdojKtzJuSkuOBQ5CNFum+zZXFwHjo+CxBC1t6zlYPgHIgFjL8ggoEQ==
-  dependencies:
-    escape-string-regexp "^1.0.5"
-    object-assign "^4.1.0"
-
-figures@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962"
-  integrity sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==
-  dependencies:
-    escape-string-regexp "^1.0.5"
-
 file-entry-cache@^6.0.1:
   version "6.0.1"
   resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
@@ -10800,13 +10736,6 @@ hard-rejection@^2.1.0:
   resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883"
   integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==
 
-has-ansi@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
-  integrity sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==
-  dependencies:
-    ansi-regex "^2.0.0"
-
 has-bigints@^1.0.1, has-bigints@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa"
@@ -11186,19 +11115,12 @@ ieee754@^1.1.13, ieee754@^1.1.4, ieee754@^1.2.1:
   resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
   integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
 
-ignore-walk@^5.0.1:
-  version "5.0.1"
-  resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-5.0.1.tgz#5f199e23e1288f518d90358d461387788a154776"
-  integrity sha512-yemi4pMf51WKT7khInJqAvsIGzoqYXblnsz0ql8tM+yi1EKYTY1evX4NAbJrLL/Aanr2HyZeluqU+Oi7MGHokw==
-  dependencies:
-    minimatch "^5.0.1"
-
-ignore-walk@^6.0.0:
-  version "6.0.3"
-  resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-6.0.3.tgz#0fcdb6decaccda35e308a7b0948645dd9523b7bb"
-  integrity sha512-C7FfFoTA+bI10qfeydT8aZbvr91vAEU+2W5BZUlzPec47oNb07SsOfwYrtxuvOYdUApPP/Qlh4DtAO51Ekk2QA==
+ignore-walk@^3.0.1:
+  version "3.0.4"
+  resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.4.tgz#c9a09f69b7c7b479a5d74ac1a3c0d4236d2a6335"
+  integrity sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ==
   dependencies:
-    minimatch "^9.0.0"
+    minimatch "^3.0.4"
 
 ignore@5.2.4, ignore@^5.0.4, ignore@^5.2.0:
   version "5.2.4"
@@ -11253,11 +11175,6 @@ imurmurhash@^0.1.4:
   resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
   integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==
 
-indent-string@^3.0.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289"
-  integrity sha512-BYqTHXTGUIvg7t1r4sJNKcbDZkL92nkXA8YtRpbjFHRHGDL/NtUeiBJMeE60kIFN/Mg8ESaWQvftaYMGJzQZCQ==
-
 indent-string@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
@@ -11708,13 +11625,6 @@ is-obj@^2.0.0:
   resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982"
   integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==
 
-is-observable@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/is-observable/-/is-observable-1.1.0.tgz#b3e986c8f44de950867cab5403f5a3465005975e"
-  integrity sha512-NqCa4Sa2d+u7BWc6CukaObG3Fh+CU9bvixbpcXYhy2VvYS7vVGIdAgnIS5Ks3A/cqk4rebLJ9s8zBstT2aKnIA==
-  dependencies:
-    symbol-observable "^1.1.0"
-
 is-path-cwd@^2.2.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb"
@@ -11747,11 +11657,6 @@ is-plain-object@^5.0.0:
   resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344"
   integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==
 
-is-promise@^2.1.0:
-  version "2.2.2"
-  resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1"
-  integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==
-
 is-regex@^1.1.4:
   version "1.1.4"
   resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958"
@@ -11801,11 +11706,6 @@ is-stream@2.0.0:
   resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3"
   integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==
 
-is-stream@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
-  integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==
-
 is-stream@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077"
@@ -11855,6 +11755,11 @@ is-unicode-supported@^0.1.0:
   resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7"
   integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==
 
+is-unicode-supported@^1.3.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz#d824984b616c292a2e198207d4a609983842f714"
+  integrity sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==
+
 is-upper-case@^2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/is-upper-case/-/is-upper-case-2.0.2.tgz#f1105ced1fe4de906a5f39553e7d3803fd804649"
@@ -12629,35 +12534,6 @@ lint-staged@^10.5.4:
     string-argv "0.3.1"
     stringify-object "^3.3.0"
 
-listr-silent-renderer@^1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz#924b5a3757153770bf1a8e3fbf74b8bbf3f9242e"
-  integrity sha512-L26cIFm7/oZeSNVhWB6faeorXhMg4HNlb/dS/7jHhr708jxlXrtrBWo4YUxZQkc6dGoxEAe6J/D3juTRBUzjtA==
-
-listr-update-renderer@^0.5.0:
-  version "0.5.0"
-  resolved "https://registry.yarnpkg.com/listr-update-renderer/-/listr-update-renderer-0.5.0.tgz#4ea8368548a7b8aecb7e06d8c95cb45ae2ede6a2"
-  integrity sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA==
-  dependencies:
-    chalk "^1.1.3"
-    cli-truncate "^0.2.1"
-    elegant-spinner "^1.0.1"
-    figures "^1.7.0"
-    indent-string "^3.0.0"
-    log-symbols "^1.0.2"
-    log-update "^2.3.0"
-    strip-ansi "^3.0.1"
-
-listr-verbose-renderer@^0.5.0:
-  version "0.5.0"
-  resolved "https://registry.yarnpkg.com/listr-verbose-renderer/-/listr-verbose-renderer-0.5.0.tgz#f1132167535ea4c1261102b9f28dac7cba1e03db"
-  integrity sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw==
-  dependencies:
-    chalk "^2.4.1"
-    cli-cursor "^2.1.0"
-    date-fns "^1.27.2"
-    figures "^2.0.0"
-
 listr2@^3.2.2:
   version "3.14.0"
   resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.14.0.tgz#23101cc62e1375fd5836b248276d1d2b51fdbe9e"
@@ -12686,21 +12562,6 @@ listr2@^4.0.5:
     through "^2.3.8"
     wrap-ansi "^7.0.0"
 
-listr@^0.14.3:
-  version "0.14.3"
-  resolved "https://registry.yarnpkg.com/listr/-/listr-0.14.3.tgz#2fea909604e434be464c50bddba0d496928fa586"
-  integrity sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA==
-  dependencies:
-    "@samverschueren/stream-to-observable" "^0.3.0"
-    is-observable "^1.1.0"
-    is-promise "^2.1.0"
-    is-stream "^1.1.0"
-    listr-silent-renderer "^1.1.1"
-    listr-update-renderer "^0.5.0"
-    listr-verbose-renderer "^0.5.0"
-    p-map "^2.0.0"
-    rxjs "^6.3.3"
-
 lit-element@^2.3.1:
   version "2.5.1"
   resolved "https://registry.yarnpkg.com/lit-element/-/lit-element-2.5.1.tgz#3fa74b121a6cd22902409ae3859b7847d01aa6b6"
@@ -12923,13 +12784,6 @@ lodash@4.17.21, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@~4.17.
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
   integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
 
-log-symbols@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18"
-  integrity sha512-mmPrW0Fh2fxOzdBbFv4g1m6pR72haFLPJ2G5SJEELf1y+iaQrDG6cWCPjy54RHYbZAt7X+ls690Kw62AdWXBzQ==
-  dependencies:
-    chalk "^1.0.0"
-
 log-symbols@^2.1.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a"
@@ -12945,15 +12799,6 @@ log-symbols@^4.0.0, log-symbols@^4.1.0:
     chalk "^4.1.0"
     is-unicode-supported "^0.1.0"
 
-log-update@^2.3.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/log-update/-/log-update-2.3.0.tgz#88328fd7d1ce7938b29283746f0b1bc126b24708"
-  integrity sha512-vlP11XfFGyeNQlmEn9tJ66rEW1coA/79m5z6BCkudjbAGE83uhAcGYrBFwfs3AdLiLzGRusRPAbSPK9xZteCmg==
-  dependencies:
-    ansi-escapes "^3.0.0"
-    cli-cursor "^2.0.0"
-    wrap-ansi "^3.0.1"
-
 log-update@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1"
@@ -13340,11 +13185,6 @@ mime@~2.5.2:
   resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe"
   integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==
 
-mimic-fn@^1.0.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
-  integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==
-
 mimic-fn@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
@@ -14384,20 +14224,13 @@ now-and-later@^2.0.0:
   dependencies:
     once "^1.3.2"
 
-npm-bundled@^1.1.1, npm-bundled@^1.1.2:
+npm-bundled@^1.0.1, npm-bundled@^1.1.1:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.2.tgz#944c78789bd739035b70baa2ca5cc32b8d860bc1"
   integrity sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==
   dependencies:
     npm-normalize-package-bin "^1.0.1"
 
-npm-bundled@^2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-2.0.1.tgz#94113f7eb342cd7a67de1e789f896b04d2c600f4"
-  integrity sha512-gZLxXdjEzE/+mOstGDqR6b0EkhJ+kM6fxM6vUuckuctuVPh80Q6pw/rSZj9s4Gex9GxWtIicO1pc8DB9KZWudw==
-  dependencies:
-    npm-normalize-package-bin "^2.0.0"
-
 npm-bundled@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-3.0.0.tgz#7e8e2f8bb26b794265028491be60321a25a39db7"
@@ -14463,32 +14296,13 @@ npm-package-arg@^9.0.0, npm-package-arg@^9.0.1:
     semver "^7.3.5"
     validate-npm-package-name "^4.0.0"
 
-npm-packlist@5.1.1:
-  version "5.1.1"
-  resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-5.1.1.tgz#79bcaf22a26b6c30aa4dd66b976d69cc286800e0"
-  integrity sha512-UfpSvQ5YKwctmodvPPkK6Fwk603aoVsf8AEbmVKAEECrfvL8SSe1A2YIwrJ6xmTHAITKPwwZsWo7WwEbNk0kxw==
-  dependencies:
-    glob "^8.0.1"
-    ignore-walk "^5.0.1"
-    npm-bundled "^1.1.2"
-    npm-normalize-package-bin "^1.0.1"
-
-npm-packlist@^5.1.0:
-  version "5.1.3"
-  resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-5.1.3.tgz#69d253e6fd664b9058b85005905012e00e69274b"
-  integrity sha512-263/0NGrn32YFYi4J533qzrQ/krmmrWwhKkzwTuM4f/07ug51odoaNjUexxO4vxlzURHcmYMH1QjvHjsNDKLVg==
-  dependencies:
-    glob "^8.0.1"
-    ignore-walk "^5.0.1"
-    npm-bundled "^2.0.0"
-    npm-normalize-package-bin "^2.0.0"
-
-npm-packlist@^7.0.0:
-  version "7.0.4"
-  resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-7.0.4.tgz#033bf74110eb74daf2910dc75144411999c5ff32"
-  integrity sha512-d6RGEuRrNS5/N84iglPivjaJPxhDbZmlbTwTDX2IbcRHG5bZCdtysYMhwiPvcF4GisXHGn7xsxv+GQ7T/02M5Q==
+npm-packlist@1.1.12, npm-packlist@5.1.1, npm-packlist@^5.1.0, npm-packlist@^7.0.0:
+  version "1.1.12"
+  resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.12.tgz#22bde2ebc12e72ca482abd67afc51eb49377243a"
+  integrity sha512-WJKFOVMeAlsU/pjXuqVdzU0WfgtIBCupkEVwn+1Y0ERAbUfWw8R4GjgVbaKnUjRoD2FoQbHOCbOyT5Mbs9Lw4g==
   dependencies:
-    ignore-walk "^6.0.0"
+    ignore-walk "^3.0.1"
+    npm-bundled "^1.0.1"
 
 npm-pick-manifest@8.0.1, npm-pick-manifest@^8.0.0, npm-pick-manifest@^8.0.1:
   version "8.0.1"
@@ -14836,13 +14650,6 @@ once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.4.0:
   dependencies:
     wrappy "1"
 
-onetime@^2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4"
-  integrity sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==
-  dependencies:
-    mimic-fn "^1.0.0"
-
 onetime@^5.1.0, onetime@^5.1.2:
   version "5.1.2"
   resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e"
@@ -15006,11 +14813,6 @@ p-map@4.0.0, p-map@^4.0.0:
   dependencies:
     aggregate-error "^3.0.0"
 
-p-map@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175"
-  integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==
-
 p-pipe@3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/p-pipe/-/p-pipe-3.1.0.tgz#48b57c922aa2e1af6a6404cb7c6bf0eb9cc8e60e"
@@ -15752,14 +15554,6 @@ promise@^7.1.1:
   dependencies:
     asap "~2.0.3"
 
-prompts@^2.3.2:
-  version "2.4.2"
-  resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069"
-  integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==
-  dependencies:
-    kleur "^3.0.3"
-    sisteransi "^1.0.5"
-
 promzard@^0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/promzard/-/promzard-0.3.0.tgz#26a5d6ee8c7dee4cb12208305acfb93ba382a9ee"
@@ -16638,14 +16432,6 @@ response-iterator@^0.2.6:
   resolved "https://registry.yarnpkg.com/response-iterator/-/response-iterator-0.2.6.tgz#249005fb14d2e4eeb478a3f735a28fd8b4c9f3da"
   integrity sha512-pVzEEzrsg23Sh053rmDUvLSkGXluZio0qu8VT6ukrYuvtjVfCbDZH9d6PGXb8HZfzdNZt8feXv/jvUzlhRgLnw==
 
-restore-cursor@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"
-  integrity sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==
-  dependencies:
-    onetime "^2.0.0"
-    signal-exit "^3.0.2"
-
 restore-cursor@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e"
@@ -16767,7 +16553,7 @@ rxjs@7.8.1, rxjs@^7.5.1, rxjs@^7.5.4, rxjs@^7.5.5, rxjs@^7.5.6, rxjs@^7.8.0:
   dependencies:
     tslib "^2.1.0"
 
-rxjs@^6.3.3, rxjs@^6.5.1, rxjs@^6.5.2, rxjs@^6.6.3:
+rxjs@^6.5.2, rxjs@^6.6.3:
   version "6.6.7"
   resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9"
   integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==
@@ -17155,11 +16941,6 @@ slash@^4.0.0:
   resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7"
   integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==
 
-slice-ansi@0.0.4:
-  version "0.0.4"
-  resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35"
-  integrity sha512-up04hB2hR92PgjpyU3y/eg91yIBILyjVY26NvvciY3EVVPjybkMszMpXQ9QAkcS3I5rtJBDLoTxxg+qvW8c7rw==
-
 slice-ansi@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787"
@@ -17600,14 +17381,6 @@ string-width@^1.0.1, string-width@^1.0.2:
     is-fullwidth-code-point "^3.0.0"
     strip-ansi "^6.0.1"
 
-string-width@^2.1.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
-  integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
-  dependencies:
-    is-fullwidth-code-point "^2.0.0"
-    strip-ansi "^4.0.0"
-
 string-width@^3.0.0, string-width@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961"
@@ -17798,11 +17571,6 @@ subscriptions-transport-ws@0.11.0:
     symbol-observable "^1.0.4"
     ws "^5.2.0 || ^6.0.0 || ^7.0.0"
 
-supports-color@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
-  integrity sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==
-
 supports-color@^5.3.0:
   version "5.5.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
@@ -17864,7 +17632,7 @@ symbol-observable@4.0.0, symbol-observable@^4.0.0:
   resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-4.0.0.tgz#5b425f192279e87f2f9b937ac8540d1984b39205"
   integrity sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==
 
-symbol-observable@^1.0.4, symbol-observable@^1.1.0:
+symbol-observable@^1.0.4:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
   integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==
@@ -19205,14 +18973,6 @@ wrap-ansi@^2.0.0:
     string-width "^1.0.1"
     strip-ansi "^3.0.1"
 
-wrap-ansi@^3.0.1:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-3.0.1.tgz#288a04d87eda5c286e060dfe8f135ce8d007f8ba"
-  integrity sha512-iXR3tDXpbnTpzjKSylUJRkLuOrEC7hwEB221cgn6wtF8wpmz28puFXAEfPT5zrjM3wahygB//VuWEr1vTkDcNQ==
-  dependencies:
-    string-width "^2.1.1"
-    strip-ansi "^4.0.0"
-
 wrap-ansi@^5.1.0:
   version "5.1.0"
   resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09"