├── .all-contributorsrc ├── .circleci ├── config.yml └── configs │ ├── e2e_test.yml │ ├── npm_publish.yml │ └── pr_validator.yml ├── .deepsource.toml ├── .eslintignore ├── .eslintrc ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── e2e_scheduled.yaml │ ├── manual_alpha_release_v6.yaml │ ├── manual_public_release.yaml │ ├── manual_public_release_v6.yaml │ ├── pr_e2e.yaml │ ├── pr_workflow.yaml │ ├── skip_e2e.yaml │ ├── skip_pr_workflow.yaml │ ├── skip_publish.yaml │ ├── tag.yaml │ ├── ~reusable_e2e_all_OS.yaml │ ├── ~reusable_e2e_by_OS.yaml │ ├── ~reusable_public_publish.yaml │ └── ~reusable_publish.yaml ├── .gitignore ├── .husky ├── .gitignore ├── pre-commit └── pre-push ├── .npmrc ├── .prettierrc.js ├── .vscode └── settings.json ├── CONTRIBUTING.md ├── CONTRIBUTORS.md ├── LICENSE ├── README.md ├── babel.config.js ├── bin ├── docs-generator.js ├── publish └── test.js ├── changelog ├── v2.md ├── v3.md ├── v4.md ├── v6.md └── v7.md ├── codecov.yml ├── faq.md ├── jest.base.js ├── jest.setup.js ├── lerna.json ├── package-lock.json ├── package.json ├── packages ├── create-flex-plugin │ ├── .prettierignore │ ├── LICENSE │ ├── README.md │ ├── __mocks__ │ │ ├── boxen.ts │ │ ├── execa.ts │ │ └── yargs.ts │ ├── bin │ │ └── create-flex-plugin │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── lib │ │ │ ├── __tests__ │ │ │ │ ├── cli.test.ts │ │ │ │ ├── commands.test.ts │ │ │ │ └── create-flex-plugin.test.ts │ │ │ ├── cli.ts │ │ │ ├── commands.ts │ │ │ └── create-flex-plugin.ts │ │ ├── prints │ │ │ ├── __tests__ │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── finalMessage.test.ts.snap │ │ │ │ └── finalMessage.test.ts │ │ │ └── finalMessage.ts │ │ └── utils │ │ │ ├── __tests__ │ │ │ ├── github.test.ts │ │ │ └── validators.test.ts │ │ │ ├── github.ts │ │ │ └── validators.ts │ ├── templates │ │ ├── core │ │ │ ├── README.md │ │ │ ├── _.gitignore │ │ │ ├── jest.config.js │ │ │ ├── public │ │ │ │ ├── appConfig.example.js │ │ │ │ └── appConfig.js │ │ │ ├── webpack.config.js │ │ │ └── webpack.dev.js │ │ ├── js │ │ │ ├── package.json │ │ │ └── src │ │ │ │ ├── DemoPlugin.js │ │ │ │ ├── components │ │ │ │ ├── CustomTaskList │ │ │ │ │ ├── CustomTaskList.Container.js │ │ │ │ │ ├── CustomTaskList.Styles.js │ │ │ │ │ └── CustomTaskList.jsx │ │ │ │ └── __tests__ │ │ │ │ │ └── CustomTaskListComponent.spec.jsx │ │ │ │ ├── index.js │ │ │ │ └── states │ │ │ │ ├── CustomTaskListState.js │ │ │ │ └── index.js │ │ ├── js2 │ │ │ ├── package.json │ │ │ └── src │ │ │ │ ├── DemoPlugin.js │ │ │ │ ├── components │ │ │ │ ├── CustomTaskList │ │ │ │ │ └── CustomTaskList.jsx │ │ │ │ └── __tests__ │ │ │ │ │ └── CustomTaskList.spec.jsx │ │ │ │ └── index.js │ │ ├── ts │ │ │ ├── package.json │ │ │ ├── src │ │ │ │ ├── DemoPlugin.tsx │ │ │ │ ├── components │ │ │ │ │ ├── CustomTaskList │ │ │ │ │ │ ├── CustomTaskList.Container.ts │ │ │ │ │ │ ├── CustomTaskList.Styles.ts │ │ │ │ │ │ └── CustomTaskList.tsx │ │ │ │ │ └── __tests__ │ │ │ │ │ │ └── CustomTaskListComponent.spec.tsx │ │ │ │ ├── index.ts │ │ │ │ └── states │ │ │ │ │ ├── CustomTaskListState.ts │ │ │ │ │ └── index.ts │ │ │ └── tsconfig.json │ │ └── ts2 │ │ │ ├── package.json │ │ │ ├── src │ │ │ ├── DemoPlugin.tsx │ │ │ ├── components │ │ │ │ ├── CustomTaskList │ │ │ │ │ └── CustomTaskList.tsx │ │ │ │ └── __tests__ │ │ │ │ │ └── CustomTaskList.spec.tsx │ │ │ └── index.ts │ │ │ └── tsconfig.json │ ├── tsconfig.json │ └── tsconfig.lint.json ├── flex-dev-utils │ ├── LICENSE │ ├── README.md │ ├── __mocks__ │ │ └── keytar.ts │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── __tests__ │ │ │ ├── axios.test.ts │ │ │ ├── credentials.test.ts │ │ │ ├── exit.test.ts │ │ │ ├── fs.test.ts │ │ │ ├── keychain.test.ts │ │ │ ├── lodash.test.ts │ │ │ ├── prints.test.ts │ │ │ ├── random.test.ts │ │ │ ├── runner.test.ts │ │ │ ├── semver.test.ts │ │ │ ├── sids.test.ts │ │ │ ├── updateNotifier.test.ts │ │ │ ├── urls.test.ts │ │ │ └── validators.test.ts │ │ ├── axios.ts │ │ ├── credentials.ts │ │ ├── env.ts │ │ ├── errors │ │ │ ├── index.ts │ │ │ └── lib │ │ │ │ ├── FlexPluginError.ts │ │ │ │ ├── UserActionError.ts │ │ │ │ ├── ValidationError.ts │ │ │ │ └── __tests__ │ │ │ │ ├── FlexPluginError.test.ts │ │ │ │ ├── UserActionError.test.ts │ │ │ │ └── ValidationError.test.ts │ │ ├── exit.ts │ │ ├── fs.ts │ │ ├── http │ │ │ ├── index.ts │ │ │ └── lib │ │ │ │ ├── __tests__ │ │ │ │ └── http.test.ts │ │ │ │ └── http.ts │ │ ├── index.ts │ │ ├── keychain.ts │ │ ├── lodash.ts │ │ ├── logger │ │ │ ├── index.ts │ │ │ └── lib │ │ │ │ ├── __tests__ │ │ │ │ ├── boxen.test.ts │ │ │ │ ├── logger.test.ts │ │ │ │ ├── strings.test.ts │ │ │ │ └── table.test.ts │ │ │ │ ├── boxen.ts │ │ │ │ ├── columnify.ts │ │ │ │ ├── logger.ts │ │ │ │ ├── strings.ts │ │ │ │ └── table.ts │ │ ├── module.d.ts │ │ ├── open.ts │ │ ├── packages │ │ │ ├── index.ts │ │ │ └── lib │ │ │ │ ├── __tests__ │ │ │ │ ├── getLatestFlexUIVersion.test.ts │ │ │ │ └── getRegistryVersion.test.ts │ │ │ │ ├── getLatestFlexUIVersion.ts │ │ │ │ └── getRegistryVersion.ts │ │ ├── prints.ts │ │ ├── progress │ │ │ ├── index.ts │ │ │ └── lib │ │ │ │ ├── __tests__ │ │ │ │ └── progress.test.ts │ │ │ │ └── progress.ts │ │ ├── questions │ │ │ ├── index.ts │ │ │ └── lib │ │ │ │ ├── __tests__ │ │ │ │ └── inquirer.test.ts │ │ │ │ └── inquirer.ts │ │ ├── random.ts │ │ ├── runner.ts │ │ ├── semver.ts │ │ ├── sids.ts │ │ ├── spawn │ │ │ ├── index.ts │ │ │ └── lib │ │ │ │ ├── __tests__ │ │ │ │ ├── node.test.ts │ │ │ │ ├── npm.test.ts │ │ │ │ ├── spawn.test.ts │ │ │ │ └── yarn.test.ts │ │ │ │ ├── node.ts │ │ │ │ ├── npm.ts │ │ │ │ ├── spawn.ts │ │ │ │ └── yarn.ts │ │ ├── telemetry │ │ │ ├── index.ts │ │ │ └── lib │ │ │ │ ├── __tests__ │ │ │ │ └── telemetry.test.ts │ │ │ │ ├── constants.ts │ │ │ │ ├── telemetry.ts │ │ │ │ └── track.ts │ │ ├── updateNotifier.ts │ │ ├── urls.ts │ │ └── validators.ts │ ├── tsconfig.json │ └── tsconfig.lint.json ├── flex-plugin-e2e-tests │ ├── LICENSE │ ├── README.md │ ├── bin │ │ └── e2e │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── core │ │ │ ├── index.ts │ │ │ ├── parameters.ts │ │ │ ├── runner.ts │ │ │ └── suites.ts │ │ ├── index.ts │ │ ├── tests │ │ │ ├── step001.ts │ │ │ ├── step002.ts │ │ │ ├── step003.ts │ │ │ ├── step004.ts │ │ │ ├── step005.ts │ │ │ ├── step006.ts │ │ │ ├── step007.ts │ │ │ ├── step008.ts │ │ │ ├── step009.ts │ │ │ ├── step010.ts │ │ │ ├── step011.ts │ │ │ ├── step012.ts │ │ │ └── step013.ts │ │ └── utils │ │ │ ├── assertion.ts │ │ │ ├── browser.ts │ │ │ ├── index.ts │ │ │ ├── pages │ │ │ ├── app.ts │ │ │ ├── index.ts │ │ │ └── view │ │ │ │ ├── admin-dashboard.ts │ │ │ │ ├── agent-desktop.ts │ │ │ │ ├── base.ts │ │ │ │ ├── plugins.ts │ │ │ │ └── twilio-console.ts │ │ │ ├── plugin-helper.ts │ │ │ ├── plugins-api.ts │ │ │ ├── serverless-api.ts │ │ │ ├── spawn.ts │ │ │ └── timers.ts │ ├── tsconfig.json │ └── tsconfig.lint.json ├── flex-plugin-scripts │ ├── LICENSE │ ├── README.md │ ├── __mocks__ │ │ └── ora.ts │ ├── bin │ │ └── flex-plugin │ ├── dev_assets │ │ ├── flex-shim.js │ │ ├── flex.css │ │ ├── flex.js │ │ ├── flex.test.ts │ │ ├── index.html │ │ └── tsconfig.json │ ├── docs.config.js │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── __tests__ │ │ │ └── index.test.ts │ │ ├── clients │ │ │ ├── __tests__ │ │ │ │ ├── accounts.test.ts │ │ │ │ ├── assets.test.ts │ │ │ │ ├── builds.test.ts │ │ │ │ ├── configurations.test.ts │ │ │ │ ├── deployments.test.ts │ │ │ │ ├── environments.test.ts │ │ │ │ ├── files.test.ts │ │ │ │ ├── governor.test.ts │ │ │ │ └── services.test.ts │ │ │ ├── accounts.ts │ │ │ ├── assets.ts │ │ │ ├── builds.ts │ │ │ ├── configurations.ts │ │ │ ├── deployments.ts │ │ │ ├── environments.ts │ │ │ ├── files.ts │ │ │ ├── governor.ts │ │ │ ├── index.ts │ │ │ ├── serverless-client.ts │ │ │ ├── serverless-types.ts │ │ │ └── services.ts │ │ ├── config │ │ │ ├── index.ts │ │ │ └── jest.config.ts │ │ ├── index.ts │ │ ├── module.d.ts │ │ ├── prints │ │ │ ├── appConfigMissing.ts │ │ │ ├── availabilityWarning.ts │ │ │ ├── buildFailure.ts │ │ │ ├── buildSuccessful.ts │ │ │ ├── deploySuccessful.ts │ │ │ ├── expectedDependencyNotFound.ts │ │ │ ├── fileTooLarge.ts │ │ │ ├── index.ts │ │ │ ├── instructionToReinstall.ts │ │ │ ├── jestNotInstalled.ts │ │ │ ├── loadPluginCountError.ts │ │ │ ├── localReactIncompatibleWithRemote.ts │ │ │ ├── packagesVersions.ts │ │ │ ├── pluginVersions.ts │ │ │ ├── preFlightByPass.ts │ │ │ ├── serverCrashed.ts │ │ │ ├── typescriptNotInstalled.ts │ │ │ ├── unbundledReactMismatch.ts │ │ │ └── validateSuccessful.ts │ │ ├── scripts │ │ │ ├── __tests__ │ │ │ │ ├── build.test.ts │ │ │ │ ├── clear.test.ts │ │ │ │ ├── deploy.test.ts │ │ │ │ ├── info.test.ts │ │ │ │ ├── list.test.ts │ │ │ │ ├── pre-localrun-check.test.ts │ │ │ │ ├── pre-script-check.test.ts │ │ │ │ ├── pre-start-check.test.ts │ │ │ │ ├── remove.test.ts │ │ │ │ ├── start.test.ts │ │ │ │ ├── test.test.ts │ │ │ │ └── validate.test.ts │ │ │ ├── build.ts │ │ │ ├── clear.ts │ │ │ ├── deploy.ts │ │ │ ├── info.ts │ │ │ ├── list.ts │ │ │ ├── pre-localrun-check.ts │ │ │ ├── pre-script-check.ts │ │ │ ├── pre-start-check.ts │ │ │ ├── remove.ts │ │ │ ├── start.ts │ │ │ ├── test.ts │ │ │ ├── test │ │ │ │ └── test.ts │ │ │ └── validate.ts │ │ └── utils │ │ │ ├── __tests__ │ │ │ ├── parser.test.ts │ │ │ ├── run.test.ts │ │ │ └── runtime.test.ts │ │ │ ├── package.ts │ │ │ ├── parser.ts │ │ │ ├── run.ts │ │ │ └── runtime.ts │ ├── tsconfig.json │ └── tsconfig.lint.json ├── flex-plugin-test │ ├── LICENSE │ ├── README.md │ ├── jest-preset.js │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── __tests__ │ │ │ └── index.test.ts │ │ ├── index.ts │ │ └── jestTransforms │ │ │ ├── babel.ts │ │ │ └── css.ts │ ├── templates │ │ └── setupTests.js │ ├── tsconfig.json │ └── tsconfig.lint.json ├── flex-plugin-utils-jest │ ├── LICENSE │ ├── README.md │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── matchers │ │ │ ├── AsymmetricMatcher.ts │ │ │ ├── __tests__ │ │ │ │ ├── toMatchPath.test.ts │ │ │ │ └── toMatchPathContaining.test.ts │ │ │ ├── index.ts │ │ │ ├── toMatchPath.ts │ │ │ └── toMatchPathContaining.ts │ │ └── utils │ │ │ └── index.ts │ ├── tsconfig.json │ └── tsconfig.lint.json ├── flex-plugin-webpack │ ├── LICENSE │ ├── README.md │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── compiler.ts │ │ ├── devServer │ │ │ ├── __tests__ │ │ │ │ ├── ipcServer.test.ts │ │ │ │ └── pluginServer.test.ts │ │ │ ├── ipcServer.ts │ │ │ ├── pluginServer.ts │ │ │ └── webpackDevServer.ts │ │ ├── index.ts │ │ ├── module.d.ts │ │ ├── plugins │ │ │ └── DelayRenderStaticPlugin.ts │ │ ├── prints │ │ │ ├── devServerSuccessful.ts │ │ │ ├── dotEnvIncorrectVariable.ts │ │ │ ├── index.ts │ │ │ └── remotePluginNotFound.ts │ │ └── webpack │ │ │ ├── __tests__ │ │ │ ├── clientVariables.test.ts │ │ │ └── webpack.config.test.ts │ │ │ ├── clientVariables.ts │ │ │ ├── pnpTs.ts │ │ │ ├── webpack.config.ts │ │ │ └── webpack.dev.ts │ ├── tsconfig.json │ └── tsconfig.lint.json ├── flex-plugin │ ├── README.md │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── lib │ │ │ ├── __tests__ │ │ │ │ └── flex-plugin.test.ts │ │ │ └── flex-plugin.ts │ │ ├── module.d.ts │ │ └── utils │ │ │ ├── __tests__ │ │ │ ├── loadCSS.test.ts │ │ │ ├── loadJS.test.ts │ │ │ └── runtime.test.ts │ │ │ ├── loadCSS.ts │ │ │ ├── loadJS.ts │ │ │ ├── runtime.ts │ │ │ └── shortid.ts │ ├── tsconfig.json │ └── tsconfig.lint.json ├── flex-plugins-api-client │ ├── .eslintrc │ ├── README.md │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── clients │ │ │ ├── __tests__ │ │ │ │ ├── client.test.ts │ │ │ │ ├── configurations.test.ts │ │ │ │ ├── configuredPluginsClient.test.ts │ │ │ │ ├── pluginVersions.test.ts │ │ │ │ ├── plugins.test.ts │ │ │ │ └── releases.test.ts │ │ │ ├── client.ts │ │ │ ├── configurations.ts │ │ │ ├── configuredPlugins.ts │ │ │ ├── index.ts │ │ │ ├── pluginVersions.ts │ │ │ ├── plugins.ts │ │ │ └── releases.ts │ │ ├── index.ts │ │ └── toolkit │ │ │ ├── __tests__ │ │ │ └── flexPluginsAPIToolkitBase.test.ts │ │ │ ├── flexPluginsAPIToolkitBase.ts │ │ │ ├── index.ts │ │ │ ├── scripts │ │ │ ├── __tests__ │ │ │ │ ├── archiveConfiguration.test.ts │ │ │ │ ├── archivePlugin.test.ts │ │ │ │ ├── archivePluginVersion.test.ts │ │ │ │ ├── createConfiguration.test.ts │ │ │ │ ├── deploy.test.ts │ │ │ │ ├── describeConfiguration.test.ts │ │ │ │ ├── describePlugin.test.ts │ │ │ │ ├── describePluginVersion.test.ts │ │ │ │ ├── describeRelease.test.ts │ │ │ │ ├── diff.test.ts │ │ │ │ ├── listConfigurations.test.ts │ │ │ │ ├── listPluginVerions.test.ts │ │ │ │ ├── listPlugins.test.ts │ │ │ │ ├── listReleases.test.ts │ │ │ │ ├── mockStore.ts │ │ │ │ └── release.test.ts │ │ │ ├── archiveConfiguration.ts │ │ │ ├── archivePlugin.ts │ │ │ ├── archivePluginVersion.ts │ │ │ ├── createConfiguration.ts │ │ │ ├── deploy.ts │ │ │ ├── describeConfiguration.ts │ │ │ ├── describePlugin.ts │ │ │ ├── describePluginVersion.ts │ │ │ ├── describeRelease.ts │ │ │ ├── diff.ts │ │ │ ├── index.ts │ │ │ ├── listConfigurations.ts │ │ │ ├── listPluginVerions.ts │ │ │ ├── listPlugins.ts │ │ │ ├── listReleases.ts │ │ │ └── release.ts │ │ │ └── tools │ │ │ ├── __tests__ │ │ │ └── diff.test.ts │ │ │ ├── diff.ts │ │ │ └── index.ts │ ├── tsconfig.json │ └── tsconfig.lint.json ├── flex-plugins-utils-env │ ├── README.md │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── index.ts │ │ └── lib │ │ │ ├── __tests__ │ │ │ └── env.test.ts │ │ │ └── env.ts │ ├── tsconfig.json │ └── tsconfig.lint.json ├── flex-plugins-utils-exception │ ├── README.md │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── index.ts │ │ └── lib │ │ │ ├── NotImplementedError.ts │ │ │ ├── TwilioApiError.ts │ │ │ ├── TwilioCliError.ts │ │ │ ├── TwilioError.ts │ │ │ └── __tests__ │ │ │ ├── NotImplementedError.test.ts │ │ │ ├── TwilioApiError.test.ts │ │ │ ├── TwilioCliError.test.ts │ │ │ └── TwilioError.test.ts │ ├── tsconfig.json │ └── tsconfig.lint.json └── plugin-flex │ ├── README.md │ ├── bin │ └── prepack │ ├── docs │ └── README.md │ ├── jest.config.js │ ├── package.json │ ├── src │ ├── __tests__ │ │ ├── clients │ │ │ ├── FlexConfigurationClient.test.ts │ │ │ └── ServerlessClient.test.ts │ │ ├── commands │ │ │ └── flex │ │ │ │ └── plugins │ │ │ │ ├── archive │ │ │ │ ├── configuration.test.ts │ │ │ │ ├── plugin-version.test.ts │ │ │ │ └── plugin.test.ts │ │ │ │ ├── build.test.ts │ │ │ │ ├── create-configuration.test.ts │ │ │ │ ├── create.test.ts │ │ │ │ ├── deploy.test.ts │ │ │ │ ├── describe │ │ │ │ ├── configuration.test.ts │ │ │ │ ├── plugin-version.test.ts │ │ │ │ ├── plugin.test.ts │ │ │ │ └── release.test.ts │ │ │ │ ├── diff.test.ts │ │ │ │ ├── list │ │ │ │ ├── configurations.test.ts │ │ │ │ ├── plugin-versions.test.ts │ │ │ │ ├── plugins.test.ts │ │ │ │ └── releases.test.ts │ │ │ │ ├── release.test.ts │ │ │ │ ├── start.test.ts │ │ │ │ ├── test.test.ts │ │ │ │ ├── upgrade-plugin.test.ts │ │ │ │ └── validate.test.ts │ │ ├── framework.ts │ │ ├── sub-commands │ │ │ ├── archive-resource.test.ts │ │ │ ├── create-configuration.test.ts │ │ │ ├── flex-plugin.test.ts │ │ │ └── information-flex-plugin.test.ts │ │ └── utils │ │ │ ├── config.test.ts │ │ │ ├── general.test.ts │ │ │ ├── parser.test.ts │ │ │ └── strings.test.ts │ ├── clients │ │ ├── FlexConfigurationClient.ts │ │ └── ServerlessClient.ts │ ├── commands │ │ └── flex │ │ │ └── plugins │ │ │ ├── archive │ │ │ ├── configuration.ts │ │ │ ├── plugin-version.ts │ │ │ └── plugin.ts │ │ │ ├── build.ts │ │ │ ├── create-configuration.ts │ │ │ ├── create.ts │ │ │ ├── deploy.ts │ │ │ ├── describe │ │ │ ├── configuration.ts │ │ │ ├── plugin-version.ts │ │ │ ├── plugin.ts │ │ │ └── release.ts │ │ │ ├── diff.ts │ │ │ ├── list │ │ │ ├── configurations.ts │ │ │ ├── plugin-versions.ts │ │ │ ├── plugins.ts │ │ │ └── releases.ts │ │ │ ├── release.ts │ │ │ ├── start.ts │ │ │ ├── test.ts │ │ │ ├── upgrade-plugin.ts │ │ │ └── validate.ts │ ├── module.d.ts │ ├── prints │ │ ├── archiveResource.ts │ │ ├── deploy.ts │ │ ├── flexPlugin.ts │ │ ├── index.ts │ │ ├── release.ts │ │ └── upgradePlugin.ts │ ├── sub-commands │ │ ├── archive-resource.ts │ │ ├── create-configuration.ts │ │ ├── flex-plugin.ts │ │ └── information-flex-plugin.ts │ └── utils │ │ ├── config.ts │ │ ├── flags.ts │ │ ├── general.ts │ │ ├── index.ts │ │ ├── parser.ts │ │ └── strings.ts │ ├── tsconfig.json │ └── tsconfig.lint.json ├── tsconfig.json ├── tsconfig.lint.json └── tsconfig.test.json /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | setup: true 4 | 5 | parameters: 6 | run_e2e_test: 7 | default: false 8 | type: boolean 9 | run_e2e_test_nightly: 10 | default: false 11 | type: boolean 12 | run_pr_validator: 13 | default: true 14 | type: boolean 15 | run_npm_publish: 16 | default: false 17 | type: boolean 18 | dist_tag: 19 | default: "alpha" 20 | type: string 21 | version_bump: 22 | default: "patch" 23 | type: string 24 | package_version: 25 | default: "" 26 | type: string 27 | flex_ui_version: 28 | default: "" 29 | type: string 30 | 31 | executors: 32 | node: 33 | parameters: 34 | tag: 35 | type: string 36 | docker: 37 | - image: cimg/node:<< parameters.tag >>-browsers 38 | macos: 39 | macos: 40 | xcode: 14.2.0 41 | 42 | orbs: 43 | split-config: bufferings/split-config@0.1.0 44 | 45 | workflows: 46 | generate-config: 47 | jobs: 48 | - split-config/generate-config: 49 | find-config-regex: \./\.circleci/configs/.*\.yml 50 | -------------------------------------------------------------------------------- /.deepsource.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [[analyzers]] 4 | name = "javascript" 5 | enabled = true 6 | 7 | [analyzers.meta] 8 | plugins = ["react"] 9 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | templates 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "twilio-ts" 4 | ], 5 | "parserOptions": { 6 | "project": "./tsconfig.lint.json" 7 | }, 8 | "overrides": [{ 9 | "files": ["*.test.ts"], 10 | "rules": { 11 | "sonarjs/no-duplicate-string": "off" 12 | } 13 | }], 14 | "rules": { 15 | "prefer-promise-reject-errors": "off", 16 | "import/no-named-as-default-member": "off", 17 | "prefer-named-capture-group": "off", 18 | "@typescript-eslint/ban-ts-comment": "off", 19 | "@typescript-eslint/triple-slash-reference": "off", 20 | "sonarjs/no-duplicate-string": "warn", 21 | "sonarjs/cognitive-complexity": "warn", 22 | "spaced-comment": [ 23 | "error", 24 | "always", 25 | { 26 | "line": { 27 | "markers": ["/"] 28 | } 29 | } 30 | ] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: ktalebian, rnairtwilio 7 | 8 | --- 9 | 10 | ### Description 11 | 12 | A clear and concise description of what the bug is. 13 | 14 | ### Versions 15 | 16 | You can find the version of your `@twilio/plugin-flex` by typing `twilio plugins`. 17 | 18 | | package | version | 19 | | ----------------------------| ------- | 20 | | `@twilio/plugin-flex` | `X.Y.Z` | 21 | | `flex-plugin-scripts` | `X.Y.Z` | 22 | | `node` | `X.Y.Z` | 23 | | `npm` | `X.Y.Z` | 24 | 25 | ### Package.json 26 | 27 | Please include the entire content of your `package.json`. 28 | 29 | 30 | ### Steps to Reproduce 31 | 32 | Please provide the steps to reproduce this bug: 33 | 34 | 1. 35 | 36 | ### Expected Behavior 37 | 38 | A clear and concise description of what you expected to happen. 39 | 40 | ### Screenshots 41 | 42 | If applicable, add screenshots to help explain your problem. 43 | 44 | ### Additional Context 45 | 46 | Add any other context about the problem here. 47 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[FEATURE REQUEST]" 5 | labels: '' 6 | assignees: ktalebian, rnairtwilio 7 | 8 | --- 9 | 10 | ### Feature Request 11 | 12 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 13 | 14 | ### Description of Solution 15 | 16 | A clear and concise description of what you want to happen. 17 | 18 | ### Additional Context 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /.github/workflows/manual_alpha_release_v6.yaml: -------------------------------------------------------------------------------- 1 | name: Manual Alpha Release v6 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | type: choice 8 | description: The Version type for PUBLIC Release 9 | options: 10 | - patch 11 | - minor 12 | - major 13 | required: true 14 | 15 | jobs: 16 | release-public-version: 17 | if: github.ref == 'refs/heads/main' 18 | uses: ./.github/workflows/~reusable_public_publish.yaml 19 | with: 20 | TAG: alpha 21 | VERSION: patch 22 | BRANCH: FLEXY-5323-6.4.2 23 | ENVIRONMENT: alpha_release 24 | SEND_NOTIFICATION: true 25 | SLACK_TITLE: 'Manual Alpha Release E2E for Version ${{ github.event.inputs.version }}' 26 | SLACK_MESSAGE: 'Release E2E' 27 | secrets: 28 | NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} 29 | SLACK_WEB_HOOK: ${{ secrets.SLACK_WEB_HOOK }} 30 | G_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | APP_ID: ${{ secrets.APP_ID }} 32 | APP_KEY: ${{ secrets.APP_KEY }} 33 | -------------------------------------------------------------------------------- /.github/workflows/skip_e2e.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_call: 3 | 4 | jobs: 5 | node20: 6 | strategy: 7 | matrix: 8 | os: [ubuntu-22.04, windows-latest, macos-latest] 9 | runs-on: ${{ matrix.os }} 10 | steps: 11 | - run: echo "e2e not required. Passing status check" 12 | -------------------------------------------------------------------------------- /.github/workflows/skip_pr_workflow.yaml: -------------------------------------------------------------------------------- 1 | name: Skip Test 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | paths: 8 | - 'lerna.json' 9 | push: 10 | branches: 11 | - main 12 | paths: 13 | - 'lerna.json' 14 | 15 | jobs: 16 | build-and-test: 17 | runs-on: ubuntu-22.04 18 | steps: 19 | - run: echo "Test execution not required. Passing status check" 20 | release-alpha-version: 21 | uses: ./.github/workflows/skip_publish.yaml 22 | e2e-test: 23 | uses: ./.github/workflows/skip_e2e.yaml 24 | -------------------------------------------------------------------------------- /.github/workflows/skip_publish.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_call: 3 | 4 | jobs: 5 | publish: 6 | runs-on: ubuntu-22.04 7 | steps: 8 | - run: echo "Skip Publish, Passing status check" -------------------------------------------------------------------------------- /.github/workflows/tag.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_call: 3 | 4 | jobs: 5 | tag: 6 | runs-on: ubuntu-22.04 7 | steps: 8 | - name: wait-job 9 | run: sleep 10s 10 | shell: bash 11 | - uses: actions/checkout@v4 12 | with: 13 | ref: main 14 | - name: Retrieve Version 15 | id: tagVersion 16 | run: | 17 | echo "version=$(awk '/version/{gsub(/("|",)/,"",$2);print $2}' lerna.json)" 18 | echo "version=$(awk '/version/{gsub(/("|",)/,"",$2);print $2}' lerna.json)" >> "$GITHUB_OUTPUT" 19 | - name: Tag version 20 | run: | 21 | git config user.name github-actions 22 | git config user.email noreply@github.com 23 | git tag -a v$VERSION_NUMBER -m "v$VERSION_NUMBER" 24 | git push origin v$VERSION_NUMBER --no-verify 25 | env: 26 | VERSION_NUMBER: ${{ steps.tagVersion.outputs.version }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Screenshots 15 | screenshots 16 | 17 | # Directory for instrumented libs generated by jscoverage/JSCover 18 | lib-cov 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | 23 | # nyc test coverage 24 | .nyc_output 25 | 26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 27 | .grunt 28 | 29 | # Bower dependency directory (https://bower.io/) 30 | bower_components 31 | 32 | # node-waf configuration 33 | .lock-wscript 34 | 35 | # Compiled binary addons (https://nodejs.org/api/addons.html) 36 | build/Release 37 | /plugin-test 38 | 39 | # Dependency directories 40 | node_modules/ 41 | jspm_packages/ 42 | 43 | # TypeScript v1 declaration files 44 | typings/ 45 | 46 | # Optional npm cache directory 47 | .npm 48 | 49 | # Optional eslint cache 50 | .eslintcache 51 | 52 | # Optional REPL history 53 | .node_repl_history 54 | 55 | # Output of 'npm pack' 56 | *.tgz 57 | 58 | # Yarn Integrity file 59 | .yarn-integrity 60 | 61 | # dotenv environment variables file 62 | .env 63 | 64 | # next.js build output 65 | .next 66 | 67 | .DS_Store 68 | lerna-debug.log 69 | dist/ 70 | plugin-test/ 71 | **/node_modules 72 | .idea 73 | .lerna_backup 74 | .ultra.cache.json 75 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | ./node_modules/.bin/ultra lint 5 | -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run test 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | message=":bookmark: Release v%s" 2 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ...require('eslint-config-twilio/rules/prettier'), 3 | tabWidth: 2, 4 | }; 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.validate": ["typescript"], 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (C) 2018, Twilio, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | "@babel/env", 4 | "@babel/typescript" 5 | ] 6 | }; 7 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | require_ci_to_pass: yes 3 | notify: 4 | wait_for_ci: yes 5 | 6 | coverage: 7 | precision: 2 8 | round: nearest 9 | ignore: 10 | - "packages/**/src/index.ts" 11 | -------------------------------------------------------------------------------- /faq.md: -------------------------------------------------------------------------------- 1 | # Troubleshooting & FAQ 2 | 3 | #### npm install fails on NPM 7 4 | 5 | If you are using `npm 7` (you can find out the version by typing `npm -v` in your terminal), then you may get the following error when you run `npm install`: 6 | 7 | ```bash 8 | npm ERR! Could not resolve dependency: 9 | npm ERR! peer react@"^16.14.0" from react-test-renderer@16.14.0 10 | npm ERR! node_modules/enzyme-adapter-react-16/node_modules/react-test-renderer 11 | npm ERR! react-test-renderer@"^16.0.0-0" from enzyme-adapter-react-16@1.15.5 12 | npm ERR! node_modules/enzyme-adapter-react-16 13 | npm ERR! enzyme-adapter-react-16@"^1.14.0" from flex-plugin-test@4.3.5-beta.0 14 | npm ERR! node_modules/flex-plugin-test 15 | npm ERR! flex-plugin-test@"^4.3.5-beta.0" from flex-plugin-scripts@4.3.5-beta.0 16 | npm ERR! node_modules/flex-plugin-scripts 17 | npm ERR! Fix the upstream dependency conflict, or retry 18 | npm ERR! this command with --force, or --legacy-peer-deps 19 | npm ERR! to accept an incorrect (and potentially broken) dependency resolution. 20 | ``` 21 | 22 | To fix this problem, update your package.json and include the `react-test-renderer` package and use the exact same version you've used for your `react` package. 23 | -------------------------------------------------------------------------------- /jest.setup.js: -------------------------------------------------------------------------------- 1 | import '@babel/polyfill'; 2 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": ["packages/*"], 3 | "version": "7.1.1", 4 | "useWorkspaces": true 5 | } 6 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/.prettierignore: -------------------------------------------------------------------------------- 1 | templates/ -------------------------------------------------------------------------------- /packages/create-flex-plugin/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (C) 2018, Twilio, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /packages/create-flex-plugin/__mocks__/boxen.ts: -------------------------------------------------------------------------------- 1 | module.exports = (message: string) => { 2 | return message; 3 | }; 4 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/__mocks__/execa.ts: -------------------------------------------------------------------------------- 1 | module.exports = jest.fn(() => { 2 | return { 3 | stdout: null, 4 | }; 5 | }); 6 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/__mocks__/yargs.ts: -------------------------------------------------------------------------------- 1 | const yargs = () => ({ 2 | alias: () => { 3 | // no-op 4 | }, 5 | usage: (msg: string, description: string, builder: () => void) => { 6 | // no-op 7 | }, 8 | parse: () => { 9 | // no-op 10 | }, 11 | }); 12 | 13 | export default yargs; 14 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/bin/create-flex-plugin: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | require = require('esm-wallaby')(module /*, options*/); 6 | 7 | const CLI = require('../dist').default; 8 | const cli = new CLI(); 9 | 10 | cli.parse(...process.argv.slice(2)); 11 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/jest.config.js: -------------------------------------------------------------------------------- 1 | const base = require('./../../jest.base.js'); 2 | const pkg = require('./package'); 3 | 4 | module.exports = { 5 | rootDir: '.', 6 | ...base(pkg), 7 | testMatch: [`/src/**/*.test.ts`], 8 | testPathIgnorePatterns: ['/templates/*'], 9 | coveragePathIgnorePatterns: ['/templates/*'], 10 | modulePathIgnorePatterns: ['/templates/'], 11 | coverageThreshold: { 12 | global: { 13 | statements: 100, 14 | branches: 98, 15 | lines: 100, 16 | functions: 100, 17 | }, 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/src/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-unused-modules */ 2 | 3 | export { default } from './lib/cli'; 4 | export { default as CreateFlexPlugin } from './lib/create-flex-plugin'; 5 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/src/lib/__tests__/cli.test.ts: -------------------------------------------------------------------------------- 1 | import CLI from '../cli'; 2 | import { createFlexPlugin } from '../create-flex-plugin'; 3 | 4 | jest.mock('../create-flex-plugin'); 5 | jest.mock('@twilio/flex-dev-utils/dist/logger/lib/logger'); 6 | 7 | describe('CLI', () => { 8 | const exit = jest.spyOn(process, 'exit').mockReturnThis(); 9 | 10 | beforeEach(() => { 11 | jest.resetAllMocks(); 12 | jest.resetModules(); 13 | }); 14 | 15 | it('should call createFlexPlugin', async () => { 16 | await new CLI().parse(); 17 | 18 | expect(createFlexPlugin).toHaveBeenCalledTimes(1); 19 | expect(exit).toHaveBeenCalledTimes(1); 20 | expect(exit).toHaveBeenCalledWith(0); 21 | }); 22 | 23 | it('should have static description', () => { 24 | expect(CLI).toHaveProperty('description'); 25 | expect(CLI.description).toContain('new Twilio Flex Plugin'); 26 | }); 27 | 28 | it('should have static flag', () => { 29 | expect(CLI).toHaveProperty('flags'); 30 | expect(CLI.flags).toHaveProperty('typescript'); 31 | }); 32 | 33 | it('should have accountSid as optional', () => { 34 | expect(CLI).toHaveProperty('flags'); 35 | expect(CLI.flags).toHaveProperty('accountSid'); 36 | expect(CLI.flags.accountSid).not.toHaveProperty('demandOption'); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/templates/core/README.md: -------------------------------------------------------------------------------- 1 | # Your custom Twilio Flex Plugin 2 | 3 | Twilio Flex Plugins allow you to customize the appearance and behavior of [Twilio Flex](https://www.twilio.com/flex). If you want to learn more about the capabilities and how to use the API, check out our [Flex documentation](https://www.twilio.com/docs/flex). 4 | 5 | ## Setup 6 | 7 | Make sure you have [Node.js](https://nodejs.org) as well as [`npm`](https://npmjs.com). We support Node >= 10.12 (and recommend the _even_ versions of Node). Afterwards, install the dependencies by running `npm install`: 8 | 9 | ```bash 10 | cd {{pluginFileName}} 11 | 12 | # If you use npm 13 | npm install 14 | ``` 15 | 16 | Next, please install the [Twilio CLI](https://www.twilio.com/docs/twilio-cli/quickstart) by running: 17 | 18 | ```bash 19 | brew tap twilio/brew && brew install twilio 20 | ``` 21 | 22 | Finally, install the [Flex Plugin extension](https://github.com/twilio-labs/plugin-flex/tree/v1-beta) for the Twilio CLI: 23 | 24 | ```bash 25 | twilio plugins:install @twilio-labs/plugin-flex 26 | ``` 27 | 28 | ## Development 29 | 30 | Run `twilio flex:plugins --help` to see all the commands we currently support. For further details on Flex Plugins refer to our documentation on the [Twilio Docs](https://www.twilio.com/docs/flex/developer/plugins/cli) page. 31 | 32 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/templates/core/_.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # Flex related ignore 64 | appConfig.js 65 | pluginsService.js 66 | build/ 67 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/templates/core/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = (config, { isProd, isDev, isTest }) => { 2 | /** 3 | * Customize the Jest by modifying the config object. 4 | * Consult https://jestjs.io/docs/en/configuration for more information. 5 | */ 6 | 7 | return config; 8 | } 9 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/templates/core/public/appConfig.example.js: -------------------------------------------------------------------------------- 1 | var appConfig = { 2 | pluginService: { 3 | enabled: true, 4 | url: '/plugins', 5 | }, 6 | ytica: false, 7 | logLevel: 'info', 8 | showSupervisorDesktopView: true, 9 | }; 10 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/templates/core/public/appConfig.js: -------------------------------------------------------------------------------- 1 | var appConfig = { 2 | pluginService: { 3 | enabled: true, 4 | url: '/plugins', 5 | }, 6 | logLevel: 'info', 7 | }; 8 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/templates/core/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = (config, { isProd, isDev, isTest }) => { 2 | /** 3 | * Customize the webpack by modifying the config object. 4 | * Consult https://webpack.js.org/configuration for more information 5 | */ 6 | 7 | return config; 8 | } 9 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/templates/core/webpack.dev.js: -------------------------------------------------------------------------------- 1 | module.exports = (config, { isProd, isDev, isTest }) => { 2 | /** 3 | * Customize the webpack dev-server by modifying the config object. 4 | * Consult https://webpack.js.org/configuration/dev-server for more information. 5 | */ 6 | 7 | return config; 8 | } 9 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/templates/js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{name}}", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "postinstall": "flex-plugin pre-script-check" 7 | }, 8 | "dependencies": { 9 | "@twilio/flex-plugin": "{{flexPluginVersion}}", 10 | "react": "16.5.2", 11 | "react-dom": "16.5.2", 12 | "react-emotion": "9.2.12" 13 | }, 14 | "devDependencies": { 15 | "@twilio/flex-plugin-scripts": "{{pluginScriptsVersion}}", 16 | "@twilio/flex-ui": "{{flexSdkVersion}}", 17 | "react-test-renderer": "16.5.2" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/templates/js/src/components/CustomTaskList/CustomTaskList.Container.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { bindActionCreators } from 'redux'; 3 | 4 | import { Actions } from '../../states/CustomTaskListState'; 5 | import CustomTaskList from './CustomTaskList'; 6 | 7 | const mapStateToProps = (state) => ({ 8 | isOpen: state['{{pluginNamespace}}'].customTaskList.isOpen, 9 | }); 10 | 11 | const mapDispatchToProps = (dispatch) => ({ 12 | dismissBar: bindActionCreators(Actions.dismissBar, dispatch), 13 | }); 14 | 15 | export default connect(mapStateToProps, mapDispatchToProps)(CustomTaskList); 16 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/templates/js/src/components/CustomTaskList/CustomTaskList.Styles.js: -------------------------------------------------------------------------------- 1 | import styled from 'react-emotion'; 2 | 3 | export const CustomTaskListComponentStyles = styled('div')` 4 | padding: 10px; 5 | margin: 0px; 6 | color: #fff; 7 | background: #000; 8 | 9 | .accented { 10 | color: red; 11 | cursor: pointer; 12 | float: right; 13 | } 14 | `; 15 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/templates/js/src/components/CustomTaskList/CustomTaskList.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import { CustomTaskListComponentStyles } from './CustomTaskList.Styles'; 5 | 6 | // It is recommended to keep components stateless and use redux for managing states 7 | const CustomTaskList = (props) => { 8 | if (!props.isOpen) { 9 | return null; 10 | } 11 | 12 | return ( 13 | 14 | This is a dismissible demo component 15 | 18 | 19 | ); 20 | }; 21 | 22 | CustomTaskList.displayName = 'CustomTaskList'; 23 | 24 | CustomTaskList.propTypes = { 25 | isOpen: PropTypes.bool.isRequired, 26 | dismissBar: PropTypes.func.isRequired, 27 | }; 28 | 29 | export default CustomTaskList; 30 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/templates/js/src/components/__tests__/CustomTaskListComponent.spec.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | 4 | import CustomTaskList from '../CustomTaskList/CustomTaskList'; 5 | 6 | describe('CustomTaskListComponent', () => { 7 | it('should render demo component', () => { 8 | const props = { 9 | isOpen: true, 10 | dismissBar: () => undefined, 11 | }; 12 | const wrapper = shallow(); 13 | expect(wrapper.render().text()).toMatch('This is a dismissible demo component'); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/templates/js/src/index.js: -------------------------------------------------------------------------------- 1 | import * as FlexPlugin from '@twilio/flex-plugin'; 2 | 3 | import {{pluginClassName}} from './{{pluginClassName}}'; 4 | 5 | FlexPlugin.loadPlugin({{pluginClassName}}); 6 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/templates/js/src/states/CustomTaskListState.js: -------------------------------------------------------------------------------- 1 | const ACTION_DISMISS_BAR = 'DISMISS_BAR'; 2 | 3 | const initialState = { 4 | isOpen: true, 5 | }; 6 | 7 | export class Actions { 8 | static dismissBar = () => ({ type: ACTION_DISMISS_BAR }); 9 | } 10 | 11 | export function reduce(state = initialState, action) { 12 | // eslint-disable-next-line sonarjs/no-small-switch 13 | switch (action.type) { 14 | case ACTION_DISMISS_BAR: { 15 | return { 16 | ...state, 17 | isOpen: false, 18 | }; 19 | } 20 | 21 | default: 22 | return state; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/templates/js/src/states/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | 3 | import { reduce as CustomTaskListReducer } from './CustomTaskListState'; 4 | 5 | // Register your redux store under a unique namespace 6 | export const namespace = '{{pluginNamespace}}'; 7 | 8 | // Combine the reducers 9 | export default combineReducers({ 10 | customTaskList: CustomTaskListReducer, 11 | }); 12 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/templates/js2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{name}}", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "postinstall": "flex-plugin pre-script-check" 7 | }, 8 | "dependencies": { 9 | "@twilio/flex-plugin": "{{flexPluginVersion}}", 10 | "react": "17.0.2", 11 | "react-dom": "17.0.2", 12 | "prop-types": "^15.7.2", 13 | "@twilio-paste/core": "^15.3.1", 14 | "@twilio-paste/icons": "^9.2.0" 15 | }, 16 | "devDependencies": { 17 | "@twilio/flex-plugin-scripts": "{{pluginScriptsVersion}}", 18 | "@twilio/flex-ui": "{{flexSdkVersion}}", 19 | "react-test-renderer": "17.0.2" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/templates/js2/src/DemoPlugin.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { FlexPlugin } from '@twilio/flex-plugin'; 3 | 4 | import CustomTaskList from './components/CustomTaskList/CustomTaskList'; 5 | 6 | const PLUGIN_NAME = '{{pluginClassName}}'; 7 | 8 | export default class {{pluginClassName}} extends FlexPlugin { 9 | constructor() { 10 | super(PLUGIN_NAME); 11 | } 12 | 13 | /** 14 | * This code is run when your plugin is being started 15 | * Use this to modify any UI components or attach to the actions framework 16 | * 17 | * @param flex { typeof import('@twilio/flex-ui') } 18 | */ 19 | async init(flex, manager) { 20 | const options = { sortOrder: -1 }; 21 | flex.AgentDesktopView.Panel1.Content.add(, options); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/templates/js2/src/components/CustomTaskList/CustomTaskList.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | import { Alert } from '@twilio-paste/core/alert'; 4 | import { Theme } from '@twilio-paste/core/theme'; 5 | import { Text } from '@twilio-paste/core/text'; 6 | 7 | const CustomTaskList = () => { 8 | const [isOpen, setIsOpen] = useState(true); 9 | if (!isOpen) { 10 | return null; 11 | } 12 | 13 | const dismiss = () => setIsOpen(false); 14 | 15 | return ( 16 | 17 | 18 | 19 | This is a dismissible demo component. 20 | 21 | 22 | 23 | ); 24 | }; 25 | 26 | export default CustomTaskList; 27 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/templates/js2/src/components/__tests__/CustomTaskList.spec.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { create, act} from 'react-test-renderer'; 3 | import { Text } from '@twilio-paste/core/text'; 4 | import { Alert } from '@twilio-paste/core/alert'; 5 | 6 | import CustomTaskList from '../CustomTaskList/CustomTaskList'; 7 | 8 | describe('CustomTaskList', () => { 9 | it('should load and display dismissable component', () => { 10 | const renderer = create(); 11 | const root = renderer.root; 12 | 13 | const element = root.findByType(Alert).findByType(Text); 14 | expect(element.props.children).toEqual('This is a dismissible demo component.'); 15 | }); 16 | 17 | it('should dismiss component', () => { 18 | const renderer = create(); 19 | const root = renderer.root; 20 | 21 | const element = root.findByType(Alert).findByType(Text); 22 | expect(element.props.children).toEqual('This is a dismissible demo component.'); 23 | 24 | // dismiss on click 25 | act(root.findByType(Alert).props.onDismiss); 26 | expect(root.findAllByType(Alert).length).toEqual(0); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/templates/js2/src/index.js: -------------------------------------------------------------------------------- 1 | import * as FlexPlugin from '@twilio/flex-plugin'; 2 | 3 | import {{pluginClassName}} from './{{pluginClassName}}'; 4 | 5 | FlexPlugin.loadPlugin({{pluginClassName}}); 6 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/templates/ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{name}}", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "postinstall": "flex-plugin pre-script-check" 7 | }, 8 | "dependencies": { 9 | "@twilio/flex-plugin": "{{flexPluginVersion}}", 10 | "react": "16.5.2", 11 | "react-dom": "16.5.2", 12 | "react-emotion": "9.2.12" 13 | }, 14 | "devDependencies": { 15 | "@twilio/flex-plugin-scripts": "{{pluginScriptsVersion}}", 16 | "@twilio/flex-ui": "{{flexSdkVersion}}", 17 | "react-test-renderer": "16.5.2", 18 | "typescript": "^4" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/templates/ts/src/components/CustomTaskList/CustomTaskList.Container.ts: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { bindActionCreators, Dispatch } from 'redux'; 3 | 4 | import { AppState } from '../../states'; 5 | import { Actions } from '../../states/CustomTaskListState'; 6 | import CustomTaskList from './CustomTaskList'; 7 | 8 | export interface StateToProps { 9 | isOpen: boolean; 10 | } 11 | 12 | export interface DispatchToProps { 13 | dismissBar: () => void; 14 | } 15 | 16 | const mapStateToProps = (state: AppState): StateToProps => ({ 17 | isOpen: state['{{pluginNamespace}}'].customTaskList.isOpen, 18 | }); 19 | 20 | const mapDispatchToProps = (dispatch: Dispatch): DispatchToProps => ({ 21 | dismissBar: bindActionCreators(Actions.dismissBar, dispatch), 22 | }); 23 | 24 | export default connect(mapStateToProps, mapDispatchToProps)(CustomTaskList); 25 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/templates/ts/src/components/CustomTaskList/CustomTaskList.Styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'react-emotion'; 2 | 3 | export const CustomTaskListComponentStyles = styled('div')` 4 | padding: 10px; 5 | margin: 0px; 6 | color: #fff; 7 | background: #000; 8 | 9 | .accented { 10 | color: red; 11 | cursor: pointer; 12 | float: right; 13 | } 14 | `; 15 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/templates/ts/src/components/CustomTaskList/CustomTaskList.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { CustomTaskListComponentStyles } from './CustomTaskList.Styles'; 4 | import { StateToProps, DispatchToProps } from './CustomTaskList.Container'; 5 | 6 | interface OwnProps { 7 | // Props passed directly to the component 8 | } 9 | 10 | // Props should be a combination of StateToProps, DispatchToProps, and OwnProps 11 | type Props = StateToProps & DispatchToProps & OwnProps; 12 | 13 | // It is recommended to keep components stateless and use redux for managing states 14 | const CustomTaskList: React.FunctionComponent = (props: Props) => { 15 | if (!props.isOpen) { 16 | return null; 17 | } 18 | 19 | return ( 20 | 21 | This is a dismissible demo component 22 | 25 | 26 | ); 27 | }; 28 | 29 | CustomTaskList.displayName = 'foo'; 30 | 31 | export default CustomTaskList; 32 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/templates/ts/src/components/__tests__/CustomTaskListComponent.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | 4 | import CustomTaskList from '../CustomTaskList/CustomTaskList'; 5 | 6 | describe('CustomTaskListComponent', () => { 7 | it('should render demo component', () => { 8 | const props = { 9 | isOpen: true, 10 | dismissBar: () => undefined, 11 | }; 12 | const wrapper = shallow(); 13 | expect(wrapper.render().text()).toMatch('This is a dismissible demo component'); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/templates/ts/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as FlexPlugin from '@twilio/flex-plugin'; 2 | 3 | import {{pluginClassName}} from './{{pluginClassName}}'; 4 | 5 | FlexPlugin.loadPlugin({{pluginClassName}}); 6 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/templates/ts/src/states/CustomTaskListState.ts: -------------------------------------------------------------------------------- 1 | import { Action } from '.'; 2 | 3 | const ACTION_DISMISS_BAR = 'DISMISS_BAR'; 4 | 5 | export interface CustomTaskListState { 6 | isOpen: boolean; 7 | } 8 | 9 | const initialState: CustomTaskListState = { 10 | isOpen: true, 11 | }; 12 | 13 | export class Actions { 14 | public static dismissBar = (): Action => ({ type: ACTION_DISMISS_BAR }); 15 | } 16 | 17 | export function reduce(state: CustomTaskListState = initialState, action: Action): CustomTaskListState { 18 | // eslint-disable-next-line sonarjs/no-small-switch 19 | switch (action.type) { 20 | case ACTION_DISMISS_BAR: { 21 | return { 22 | ...state, 23 | isOpen: false, 24 | }; 25 | } 26 | 27 | default: 28 | return state; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/templates/ts/src/states/index.ts: -------------------------------------------------------------------------------- 1 | import { AppState as FlexAppState } from '@twilio/flex-ui'; 2 | import { combineReducers, Action as ReduxAction } from 'redux'; 3 | 4 | import { CustomTaskListState, reduce as CustomTaskListReducer } from './CustomTaskListState'; 5 | 6 | // Register your redux store under a unique namespace 7 | export const namespace = '{{pluginNamespace}}'; 8 | 9 | // Extend this payload to be of type that your ReduxAction is 10 | export interface Action extends ReduxAction { 11 | payload?: any; 12 | } 13 | 14 | // Register all component states under the namespace 15 | export interface AppState { 16 | flex: FlexAppState; 17 | '{{pluginNamespace}}': { 18 | customTaskList: CustomTaskListState; 19 | // Other states 20 | }; 21 | } 22 | 23 | // Combine the reducers 24 | export default combineReducers({ 25 | customTaskList: CustomTaskListReducer, 26 | }); 27 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/templates/ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "src", 4 | "rootDir": ".", 5 | "outDir": "build", 6 | "target": "es5", 7 | "lib": [ 8 | "dom", 9 | "dom.iterable", 10 | "esnext" 11 | ], 12 | "allowJs": true, 13 | "skipLibCheck": true, 14 | "esModuleInterop": true, 15 | "allowSyntheticDefaultImports": true, 16 | "strict": true, 17 | "forceConsistentCasingInFileNames": true, 18 | "module": "esnext", 19 | "moduleResolution": "node", 20 | "resolveJsonModule": true, 21 | "isolatedModules": true, 22 | "noEmit": true, 23 | "jsx": "preserve", 24 | "noUnusedLocals": false, 25 | "noUnusedParameters": false 26 | }, 27 | "include": [ 28 | "./src/**/*" 29 | ], 30 | "exclude": [ 31 | "./**/*.test.ts", 32 | "./**/*.test.tsx", 33 | "./**/__mocks__/*.ts", 34 | "./**/__mocks__/*.tsx" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/templates/ts2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{name}}", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "postinstall": "flex-plugin pre-script-check" 7 | }, 8 | "dependencies": { 9 | "@twilio/flex-plugin": "{{flexPluginVersion}}", 10 | "react": "17.0.2", 11 | "react-dom": "17.0.2", 12 | "prop-types": "^15.7.2", 13 | "@twilio-paste/core": "^15.3.1", 14 | "@twilio-paste/icons": "^9.2.0" 15 | }, 16 | "devDependencies": { 17 | "@twilio/flex-plugin-scripts": "{{pluginScriptsVersion}}", 18 | "@twilio/flex-ui": "{{flexSdkVersion}}", 19 | "react-test-renderer": "17.0.2", 20 | "typescript": "^4" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/templates/ts2/src/DemoPlugin.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import * as Flex from '@twilio/flex-ui'; 3 | import { FlexPlugin } from '@twilio/flex-plugin'; 4 | 5 | import CustomTaskList from './components/CustomTaskList/CustomTaskList'; 6 | 7 | const PLUGIN_NAME = '{{pluginClassName}}'; 8 | 9 | export default class {{pluginClassName}} extends FlexPlugin { 10 | constructor() { 11 | super(PLUGIN_NAME); 12 | } 13 | 14 | /** 15 | * This code is run when your plugin is being started 16 | * Use this to modify any UI components or attach to the actions framework 17 | * 18 | * @param flex { typeof Flex } 19 | */ 20 | async init(flex: typeof Flex, manager: Flex.Manager): Promise { 21 | const options: Flex.ContentFragmentProps = { sortOrder: -1 }; 22 | flex.AgentDesktopView.Panel1.Content.add(, options); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/templates/ts2/src/components/CustomTaskList/CustomTaskList.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | import { Alert } from '@twilio-paste/core/alert'; 4 | import { Theme } from '@twilio-paste/core/theme'; 5 | import { Text } from '@twilio-paste/core/text'; 6 | 7 | const CustomTaskList = (): JSX.Element | null => { 8 | const [isOpen, setIsOpen] = useState(true); 9 | if (!isOpen) { 10 | return null; 11 | } 12 | 13 | const dismiss = () => setIsOpen(false); 14 | 15 | return ( 16 | 17 | 18 | 19 | This is a dismissible demo component. 20 | 21 | 22 | 23 | ); 24 | }; 25 | 26 | CustomTaskList.displayName = 'foo'; 27 | 28 | export default CustomTaskList; 29 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/templates/ts2/src/components/__tests__/CustomTaskList.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { create, act} from 'react-test-renderer'; 3 | import { Text } from '@twilio-paste/core/text'; 4 | import { Alert } from '@twilio-paste/core/alert'; 5 | 6 | import CustomTaskList from '../CustomTaskList/CustomTaskList'; 7 | 8 | describe('CustomTaskList', () => { 9 | it('should load and display dismissable component', () => { 10 | const renderer = create(); 11 | const root = renderer.root; 12 | 13 | const element = root.findByType(Alert).findByType(Text); 14 | expect(element.props.children).toEqual('This is a dismissible demo component.'); 15 | }); 16 | 17 | it('should dismiss component', () => { 18 | const renderer = create(); 19 | const root = renderer.root; 20 | 21 | const element = root.findByType(Alert).findByType(Text); 22 | expect(element.props.children).toEqual('This is a dismissible demo component.'); 23 | 24 | // dismiss on click 25 | act(root.findByType(Alert).props.onDismiss); 26 | expect(root.findAllByType(Alert).length).toEqual(0); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/templates/ts2/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as FlexPlugin from '@twilio/flex-plugin'; 2 | 3 | import {{pluginClassName}} from './{{pluginClassName}}'; 4 | 5 | FlexPlugin.loadPlugin({{pluginClassName}}); 6 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/templates/ts2/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "src", 4 | "rootDir": ".", 5 | "outDir": "build", 6 | "target": "es5", 7 | "lib": [ 8 | "dom", 9 | "dom.iterable", 10 | "esnext" 11 | ], 12 | "allowJs": true, 13 | "skipLibCheck": true, 14 | "esModuleInterop": true, 15 | "allowSyntheticDefaultImports": true, 16 | "strict": true, 17 | "forceConsistentCasingInFileNames": true, 18 | "module": "esnext", 19 | "moduleResolution": "node", 20 | "resolveJsonModule": true, 21 | "isolatedModules": true, 22 | "noEmit": true, 23 | "jsx": "preserve", 24 | "noUnusedLocals": false, 25 | "noUnusedParameters": false 26 | }, 27 | "include": [ 28 | "./src/**/*" 29 | ], 30 | "exclude": [ 31 | "./**/*.test.ts", 32 | "./**/*.test.tsx", 33 | "./**/__mocks__/*.ts", 34 | "./**/__mocks__/*.tsx" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "baseUrl": ".", 6 | "rootDir": "src", 7 | "esModuleInterop": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/create-flex-plugin/tsconfig.lint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": [ 4 | "src" 5 | ], 6 | "exclude": [ 7 | "node_modules", 8 | "dist", 9 | "templates" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (C) 2018, Twilio, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /packages/flex-dev-utils/README.md: -------------------------------------------------------------------------------- 1 | ![npm](https://img.shields.io/npm/v/flex-dev-utils.svg?style=square) 2 | ![npm](https://img.shields.io/npm/dt/flex-dev-utils.svg?style=square) 3 | [![NpmLicense](https://img.shields.io/npm/l/flex-dev-utils.svg?style=square)](../../LICENSE) 4 | 5 | # Flex Dev Utils 6 | 7 | Common development utility functions and scripts for Flex plugin creation. 8 | 9 | ## Installation 10 | 11 | These utilities are included by default when you use flex-plugin. 12 | 13 | ## Contributing 14 | 15 | Make sure to follow the instructions in the [main repository](https://github.com/twilio/flex-plugin-builder#contributing) to set up the project. 16 | 17 | ```bash 18 | # Install dependencies and link local packages with each other 19 | cd packages/flex-dev-utils 20 | npx lerna bootstrap 21 | 22 | # Run tests 23 | npm test 24 | 25 | # To use your local package in a different project 26 | npm link 27 | # then in a different project 28 | npm link flex-dev-utils 29 | ``` 30 | 31 | ## Contributors 32 | 33 | Thank you to all the lovely contributors to this project. Please check the main repository to see [all contributors](https://github.com/twilio/flex-plugin-builder#contributors). 34 | 35 | ## License 36 | 37 | [MIT](../../LICENSE) 38 | 39 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/__mocks__/keytar.ts: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | module.exports = { 5 | findCredentials: (service: string) => Promise.resolve([]), 6 | setPassword: (service: string, account: string, password: string) => Promise.resolve(), 7 | deletePassword: (service: string, account: string) => Promise.resolve(true) 8 | }; 9 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/jest.config.js: -------------------------------------------------------------------------------- 1 | const base = require('./../../jest.base.js'); 2 | const pkg = require('./package'); 3 | 4 | module.exports = { 5 | rootDir: '.', 6 | ...base(pkg), 7 | coverageThreshold: { 8 | global: { 9 | statements: 94, 10 | branches: 93, 11 | lines: 94, 12 | functions: 84, 13 | }, 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/__tests__/axios.test.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import MockAdapter from 'axios-mock-adapter'; 3 | 4 | import * as Axios from '../axios'; 5 | 6 | describe('axios', () => { 7 | it('should have the keys', () => { 8 | expect(Axios.default).toBe(axios); 9 | expect(Axios.MockAdapter).toBe(MockAdapter); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/__tests__/exit.test.ts: -------------------------------------------------------------------------------- 1 | import exitScript from '../exit'; 2 | 3 | describe('exit', () => { 4 | // @ts-ignore 5 | const exit = jest.spyOn(process, 'exit').mockImplementation(() => { 6 | /* no-op */ 7 | }); 8 | const OLD_ENV = process.env; 9 | const OLD_ARGV = process.argv; 10 | 11 | beforeEach(() => { 12 | jest.resetAllMocks(); 13 | jest.resetModules(); 14 | 15 | process.env = { ...OLD_ENV }; 16 | process.argv = [...OLD_ARGV]; 17 | }); 18 | 19 | it('should not quit if arg has the flag', () => { 20 | exitScript(123, ['--no-process-exit']); 21 | 22 | expect(exit).not.toHaveBeenCalled(); 23 | }); 24 | 25 | it('should quit by default', () => { 26 | exitScript(123); 27 | 28 | expect(exit).toHaveBeenCalledTimes(1); 29 | expect(exit).toHaveBeenCalledWith(123); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/__tests__/lodash.test.ts: -------------------------------------------------------------------------------- 1 | import * as lodash from '../lodash'; 2 | 3 | describe('lodash', () => { 4 | it('should have the keys', () => { 5 | expect(lodash).toHaveProperty('camelCase'); 6 | expect(lodash).toHaveProperty('upperFirst'); 7 | expect(lodash).toHaveProperty('merge'); 8 | expect(lodash).toHaveProperty('clone'); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/__tests__/prints.test.ts: -------------------------------------------------------------------------------- 1 | import * as prints from '../prints'; 2 | import * as strings from '../logger'; 3 | 4 | jest.mock('../logger'); 5 | 6 | describe('prints', () => { 7 | beforeEach(() => { 8 | jest.resetAllMocks(); 9 | jest.resetModules(); 10 | }); 11 | 12 | describe('printList', () => { 13 | it('should call printList with no lines', () => { 14 | const multilineString = jest.spyOn(strings, 'multilineString'); 15 | 16 | prints.printList(); 17 | expect(multilineString).toHaveBeenCalledTimes(1); 18 | expect(multilineString).toHaveBeenCalledWith(); 19 | }); 20 | 21 | it('should call printList with 2 lines', () => { 22 | const multilineString = jest.spyOn(strings, 'multilineString'); 23 | 24 | prints.printList('item1', 'item2'); 25 | expect(multilineString).toHaveBeenCalledTimes(1); 26 | expect(multilineString).toHaveBeenCalledWith(expect.any(String), expect.any(String)); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/__tests__/semver.test.ts: -------------------------------------------------------------------------------- 1 | import * as semver from '../semver'; 2 | 3 | describe('semver', () => { 4 | describe('versionSatisfiesRange', () => { 5 | it('should satisfy the ranges', () => { 6 | expect(semver.versionSatisfiesRange('1.0.0', '>=1.0.0')).toEqual(true); 7 | expect(semver.versionSatisfiesRange('1.0.0-alpha.0', '>=0.9.0')).toEqual(true); 8 | expect(semver.versionSatisfiesRange('1.0.0', '1.0.0')).toEqual(true); 9 | expect(semver.versionSatisfiesRange('1.0.0', '>=0.9.0')).toEqual(true); 10 | }); 11 | 12 | it('should not satisfy the ranges', () => { 13 | expect(semver.versionSatisfiesRange('1.0.0', '>2.0.0')).toEqual(false); 14 | expect(semver.versionSatisfiesRange('1.0.0-alpha.0', '>=2.0.0')).toEqual(false); 15 | expect(semver.versionSatisfiesRange('1.0.0', '>1.0.0')).toEqual(false); 16 | }); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/axios.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/order */ 2 | import MockAdapter from 'axios-mock-adapter'; 3 | import axios, { AxiosRequestConfig, AxiosInstance } from 'axios'; 4 | 5 | export default axios; 6 | export { AxiosRequestConfig, AxiosInstance, MockAdapter }; 7 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/env.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-unused-modules */ 2 | export { env as default, default as env, Environment, Lifecycle, Region } from '@twilio/flex-plugins-utils-env'; 3 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/errors/index.ts: -------------------------------------------------------------------------------- 1 | export { default as FlexPluginError } from './lib/FlexPluginError'; 2 | export { default as ValidationError } from './lib/ValidationError'; 3 | export { default as UserActionError } from './lib/UserActionError'; 4 | export { TwilioError, TwilioApiError, NotImplementedError, TwilioCliError } from '@twilio/flex-plugins-utils-exception'; 5 | 6 | export default {}; 7 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/errors/lib/FlexPluginError.ts: -------------------------------------------------------------------------------- 1 | import { TwilioError } from '@twilio/flex-plugins-utils-exception'; 2 | 3 | import { AppPackageJson, readAppPackageJson } from '../../fs'; 4 | import { logger } from '../../logger'; 5 | 6 | export default class FlexPluginError extends TwilioError { 7 | private readonly pkg: AppPackageJson | null; 8 | 9 | constructor(msg?: string) { 10 | /* c8 ignore next */ 11 | super(msg); 12 | 13 | try { 14 | this.pkg = readAppPackageJson(); 15 | } catch (e) { 16 | this.pkg = null; 17 | } 18 | 19 | Object.setPrototypeOf(this, FlexPluginError.prototype); 20 | } 21 | 22 | public print = (): void => { 23 | logger.error(this.message); 24 | }; 25 | 26 | public details = (): void => { 27 | const { headline } = logger.coloredStrings; 28 | if (this.pkg) { 29 | const allDeps = { ...this.pkg.dependencies, ...this.pkg.devDependencies }; 30 | const names = ['@twilio/flex-plugin', '@twilio/flex-plugin-scripts']; 31 | 32 | logger.newline(); 33 | logger.info(`Your plugin ${this.pkg.name} is using the following versions:`); 34 | logger.newline(); 35 | names.forEach((name) => logger.info(`\t ${headline(`"${name}": "${allDeps[name]}"`)}`)); 36 | logger.newline(); 37 | } 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/errors/lib/UserActionError.ts: -------------------------------------------------------------------------------- 1 | import FlexPluginError from './FlexPluginError'; 2 | 3 | export default class UserActionError extends FlexPluginError { 4 | public readonly reason: string; 5 | 6 | constructor(reason: string, message?: string) { 7 | /* c8 ignore next */ 8 | super(message || reason); 9 | 10 | this.reason = reason; 11 | 12 | Object.setPrototypeOf(this, UserActionError.prototype); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/errors/lib/ValidationError.ts: -------------------------------------------------------------------------------- 1 | import FlexPluginError from './FlexPluginError'; 2 | 3 | export default class ValidationError extends FlexPluginError {} 4 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/errors/lib/__tests__/UserActionError.test.ts: -------------------------------------------------------------------------------- 1 | import FlexPluginError from '../FlexPluginError'; 2 | import UserActionError from '../UserActionError'; 3 | 4 | describe('UserActionError', () => { 5 | const errMsg = 'the-reason'; 6 | 7 | beforeEach(() => { 8 | jest.resetAllMocks(); 9 | }); 10 | 11 | it('should extend FlexPluginError', () => { 12 | expect(new UserActionError('')).toBeInstanceOf(FlexPluginError); 13 | }); 14 | 15 | it('should pass reason to message', () => { 16 | const err = new UserActionError(errMsg); 17 | 18 | expect(err.reason).toEqual(errMsg); 19 | expect(err.message).toEqual(errMsg); 20 | }); 21 | 22 | it('should set reason and message', () => { 23 | const err = new UserActionError(errMsg, 'the-message'); 24 | 25 | expect(err.reason).toEqual(errMsg); 26 | expect(err.message).toEqual('the-message'); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/errors/lib/__tests__/ValidationError.test.ts: -------------------------------------------------------------------------------- 1 | import FlexPluginError from '../FlexPluginError'; 2 | import ValidationError from '../ValidationError'; 3 | 4 | describe('ValidationError', () => { 5 | beforeEach(() => { 6 | jest.resetAllMocks(); 7 | }); 8 | 9 | it('should extend FlexPluginError', () => { 10 | expect(new ValidationError()).toBeInstanceOf(FlexPluginError); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/exit.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Exits unless --no-process-exit flag is provided 3 | * 4 | * @param exitCode the exitCode 5 | * @param args the process argument 6 | */ 7 | const exit = (exitCode: number, args: string[] = []): void => { 8 | // Exit if not an embedded script 9 | if (!args.includes('--no-process-exit')) { 10 | // eslint-disable-next-line no-process-exit 11 | process.exit(exitCode); 12 | } 13 | }; 14 | 15 | export default exit; 16 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/http/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-unused-modules */ 2 | export { 3 | default as HttpClient, 4 | HttpClientConfig, 5 | OptionalHttpClientConfig, 6 | AuthConfig, 7 | Pagination, 8 | PaginationMeta, 9 | Meta, 10 | } from './lib/http'; 11 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/lodash.ts: -------------------------------------------------------------------------------- 1 | import * as lodash from 'lodash'; 2 | 3 | export const { camelCase } = lodash; 4 | export const { upperFirst } = lodash; 5 | export const { merge } = lodash; 6 | export const { clone } = lodash; 7 | export const { cloneDeep } = lodash; 8 | 9 | export default lodash; 10 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/logger/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-unused-modules */ 2 | 3 | import * as _boxen from './lib/boxen'; 4 | 5 | const boxen = { 6 | error: _boxen.error, 7 | warning: _boxen.warning, 8 | info: _boxen.info, 9 | print: _boxen.print, 10 | }; 11 | 12 | export { Logger, coloredStrings } from './lib/logger'; 13 | export { default as chalk } from 'chalk'; 14 | export { default as logger } from './lib/logger'; 15 | export { default as strings, multilineString, singleLineString } from './lib/strings'; 16 | export { default as table, printArray, printObjectArray } from './lib/table'; 17 | export { default as columnify } from './lib/columnify'; 18 | export { boxen }; 19 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/logger/lib/__tests__/strings.test.ts: -------------------------------------------------------------------------------- 1 | import * as strings from '../strings'; 2 | 3 | describe('strings', () => { 4 | describe('multilineString', () => { 5 | it('should return single line string for one argument', () => { 6 | const result = strings.multilineString('line1'); 7 | 8 | expect(result).toEqual('line1'); 9 | }); 10 | 11 | it('should return single line string for multiple arguments', () => { 12 | const result = strings.multilineString('line1', 'line2', 'line3'); 13 | 14 | expect(result).toEqual('line1\r\nline2\r\nline3'); 15 | }); 16 | }); 17 | 18 | describe('stringLineString', () => { 19 | it('should return single line for one argument', () => { 20 | const result = strings.singleLineString('line1'); 21 | 22 | expect(result).toEqual('line1'); 23 | }); 24 | 25 | it('should return string line for multiple arguments', () => { 26 | const result = strings.singleLineString('line1', 'line2', 'line3'); 27 | 28 | expect(result).toEqual('line1 line2 line3'); 29 | }); 30 | 31 | it('should trim correctly', () => { 32 | const result = strings.singleLineString(' line1 ', ' line2', 'line3 '); 33 | 34 | expect(result).toEqual('line1 line2 line3'); 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/logger/lib/columnify.ts: -------------------------------------------------------------------------------- 1 | import columnify from 'columnify'; 2 | 3 | export default columnify; 4 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/logger/lib/strings.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Converts an array of arguments into a multiline string 3 | * 4 | * @param args the lines to print 5 | */ 6 | export const multilineString = (...args: string[]): string => args.join('\r\n'); 7 | 8 | /** 9 | * Converts an array of string into a single lin 10 | * @param args the lines to print 11 | */ 12 | export const singleLineString = (...args: string[]): string => 13 | args.map((arg, index) => (index === 0 ? arg.trim() : ` ${arg.trim()}`)).join(''); 14 | 15 | export default { 16 | multilineString, 17 | singleLineString, 18 | }; 19 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/module.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'copy-template-dir'; 2 | declare module 'daemonize-process'; 3 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/open.ts: -------------------------------------------------------------------------------- 1 | import open from 'open'; 2 | 3 | /* c8 ignore next */ 4 | export default open; 5 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/packages/index.ts: -------------------------------------------------------------------------------- 1 | export { default as getRegistryVersion } from './lib/getRegistryVersion'; 2 | export { default as getLatestFlexUIVersion } from './lib/getLatestFlexUIVersion'; 3 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/packages/lib/getLatestFlexUIVersion.ts: -------------------------------------------------------------------------------- 1 | import semver from 'semver'; 2 | 3 | import getRegistryVersion, { Tag } from './getRegistryVersion'; 4 | 5 | /** 6 | * Returns the latest flex ui version for a given major 7 | * Will search through latest, beta, and alpha versions for a matching verion (in that order) 8 | * @param version the flex ui major version 9 | */ 10 | export default async function getLatestFlexUIVersion(majorVersion: 1 | 2 | 3 | 4): Promise { 11 | const tags: Tag[] = ['latest', 'beta', 'alpha']; 12 | for (const tag of tags) { 13 | const pkg = await getRegistryVersion('@twilio/flex-ui', tag); 14 | if (!pkg || !pkg.version) { 15 | continue; 16 | } 17 | if (semver.coerce(pkg.version as string)?.major === majorVersion) { 18 | return pkg.version as string; 19 | } 20 | } 21 | 22 | throw new Error(`The major version you requested for flex ui (${majorVersion}) does not exist.`); 23 | } 24 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/packages/lib/getRegistryVersion.ts: -------------------------------------------------------------------------------- 1 | import packageJson from 'package-json'; 2 | 3 | export type Tag = 'latest' | 'beta' | 'alpha'; 4 | 5 | /** 6 | * Fetches the version corresponding to the dependency inside the flex-ui package.json 7 | * @param name the package to check 8 | */ 9 | export default async function getRegistryVersion( 10 | module: string, 11 | tag: Tag = 'latest', 12 | ): Promise { 13 | return packageJson(module, { version: tag }); 14 | } 15 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/prints.ts: -------------------------------------------------------------------------------- 1 | import { logger, multilineString } from './logger'; 2 | 3 | /** 4 | * Prints the lines in a numbered list: 5 | * 1. line 1 6 | * 2. line 2 7 | * 3. line 3 8 | * 9 | * @param lines the lines to print 10 | */ 11 | export const printList = (...lines: string[]): void => { 12 | const digitColor = logger.coloredStrings.digit; 13 | 14 | lines = lines.map((line, index) => `\t ${digitColor((index + 1).toString())}. ${line}`); 15 | logger.newline(); 16 | logger.info(multilineString(...lines)); 17 | logger.newline(); 18 | }; 19 | 20 | export default {}; 21 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/progress/index.ts: -------------------------------------------------------------------------------- 1 | export { default as progress } from './lib/progress'; 2 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/questions/index.ts: -------------------------------------------------------------------------------- 1 | export { default as inquirer, confirm, prompt, choose, Question } from './lib/inquirer'; 2 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/random.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Generates a random string 3 | * 4 | * @param n the length of the string 5 | * @private 6 | */ 7 | export const _randomGenerator = (n: number): string => Math.random().toString(26).slice(2).substring(0, n); 8 | 9 | /** 10 | * Generates a random string; if a list is provided, ensures the string is not in the list 11 | * 12 | * @param length the length 13 | * @param list the list to ensure the new string is unique 14 | */ 15 | export const randomString = (length: number, list: string[] = []): string => { 16 | let str = _randomGenerator(length); 17 | 18 | while (list.includes(str)) { 19 | str = _randomGenerator(length); 20 | } 21 | 22 | return str; 23 | }; 24 | 25 | export default randomString; 26 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/runner.ts: -------------------------------------------------------------------------------- 1 | import { logger } from './logger'; 2 | import { FlexPluginError } from './errors'; 3 | 4 | // eslint-disable-next-line import/no-unused-modules 5 | export type Callback = (...argv: string[]) => void; 6 | 7 | /** 8 | * A runner that calls a callable function, and handles the exception 9 | * @param callback 10 | * @param args 11 | */ 12 | export default async (callback: Callback, ...args: string[]): Promise => { 13 | try { 14 | return await callback(...args); 15 | } catch (e) { 16 | if (e instanceof FlexPluginError) { 17 | e.print(); 18 | if (process.env.DEBUG) { 19 | e.details(); 20 | } 21 | } else { 22 | logger.error(e); 23 | } 24 | 25 | // eslint-disable-next-line no-process-exit 26 | return process.exit(1); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/semver.ts: -------------------------------------------------------------------------------- 1 | import semver, { ReleaseType, SemVer } from 'semver'; 2 | 3 | export default semver; 4 | 5 | // eslint-disable-next-line import/no-unused-modules 6 | export { ReleaseType, SemVer }; 7 | 8 | /** 9 | * Checks whether the provided version satisfies the given range. The provided version is coerced first 10 | * @param version the version to test 11 | * @param range the range to check 12 | */ 13 | export const versionSatisfiesRange = (version: string, range: string): boolean => { 14 | return semver.satisfies(semver.coerce(version)?.version as string, range); 15 | }; 16 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/sids.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-unused-modules */ 2 | 3 | type Null = null | undefined; 4 | 5 | const SID_REGEX = /^[A-Z]{2}[0-9A-Fa-c]{32}$/i; 6 | 7 | /** 8 | * Validates the string is a valid sid 9 | * 10 | * @param sid the sid 11 | */ 12 | export const isValidSid = (sid: string | Null): boolean => Boolean(sid && /^[A-Z]{2}[0-9a-f]{32}$/.test(sid)); 13 | 14 | /** 15 | * Checks if the provided string is of type Sid 16 | * @param sid the sid to check 17 | */ 18 | export const looksLikeSid = (sid: string): boolean => { 19 | return Boolean(sid && SID_REGEX.test(sid)); 20 | }; 21 | 22 | /** 23 | * Validates sid is of type prefix provided 24 | * 25 | * @param sid the sid 26 | * @param prefix the prefix of the sid 27 | */ 28 | export const isSidOfType = (sid: string | Null, prefix: string | Null): boolean => { 29 | return Boolean(sid && prefix && isValidSid(sid) && prefix.toUpperCase() === sid.substr(0, 2)); 30 | }; 31 | 32 | /** 33 | * Prefix of Sids 34 | */ 35 | export const SidPrefix = { 36 | AccountSid: 'AC', 37 | ApiKey: 'SK', 38 | ServiceSid: 'ZS', 39 | EnvironmentSid: 'ZE', 40 | BuildSid: 'ZB', 41 | FileSid: 'ZH', 42 | VersionSid: 'ZN', 43 | DeploymentSid: 'ZD', 44 | }; 45 | 46 | export default { 47 | isValidSid, 48 | isSidOfType, 49 | looksLikeSid, 50 | SidPrefix, 51 | }; 52 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/spawn/index.ts: -------------------------------------------------------------------------------- 1 | export { default as spawn, SpawnPromise, SpawnReturn } from './lib/spawn'; 2 | export { default as node } from './lib/node'; 3 | export { default as npm } from './lib/npm'; 4 | export { default as yarn } from './lib/yarn'; 5 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/spawn/lib/__tests__/node.test.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-named-as-default 2 | import node from '../node'; 3 | 4 | jest.mock('execa'); 5 | 6 | // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports 7 | const execa = require('execa'); 8 | 9 | describe('node', () => { 10 | const args = ['arg1', 'arg2']; 11 | const spawnResult = { 12 | exitCode: 123, 13 | stdout: 'the-stdout', 14 | stderr: 'the-stderr', 15 | }; 16 | 17 | beforeEach(() => { 18 | jest.resetAllMocks(); 19 | jest.restoreAllMocks(); 20 | }); 21 | 22 | it('should spawn node', async () => { 23 | execa.mockResolvedValue(spawnResult); 24 | 25 | const resp = await node(args); 26 | expect(execa).toHaveBeenCalledTimes(1); 27 | expect(execa).toHaveBeenCalledWith('node', args, expect.any(Object)); 28 | expect(resp).toMatchObject(spawnResult); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/spawn/lib/__tests__/npm.test.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-named-as-default 2 | import npm from '../npm'; 3 | 4 | jest.mock('execa'); 5 | 6 | // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports 7 | const execa = require('execa'); 8 | 9 | describe('spawn', () => { 10 | const args = ['arg1', 'arg2']; 11 | const spawnResult = { 12 | exitCode: 123, 13 | stdout: 'the-stdout', 14 | stderr: 'the-stderr', 15 | }; 16 | 17 | beforeEach(() => { 18 | jest.resetAllMocks(); 19 | jest.restoreAllMocks(); 20 | }); 21 | 22 | it('should spawn npm', async () => { 23 | execa.mockResolvedValue(spawnResult); 24 | 25 | const resp = await npm(args); 26 | expect(execa).toHaveBeenCalledTimes(1); 27 | expect(execa).toHaveBeenCalledWith('npm', args, expect.any(Object)); 28 | expect(resp).toMatchObject(spawnResult); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/spawn/lib/__tests__/yarn.test.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-named-as-default 2 | import yarn from '../yarn'; 3 | 4 | jest.mock('execa'); 5 | 6 | // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports 7 | const execa = require('execa'); 8 | 9 | describe('spawn', () => { 10 | const args = ['arg1', 'arg2']; 11 | const spawnResult = { 12 | exitCode: 123, 13 | stdout: 'the-stdout', 14 | stderr: 'the-stderr', 15 | }; 16 | 17 | beforeEach(() => { 18 | jest.resetAllMocks(); 19 | jest.restoreAllMocks(); 20 | }); 21 | 22 | it('should spawn yarn', async () => { 23 | execa.mockResolvedValue(spawnResult); 24 | 25 | const resp = await yarn(args); 26 | expect(execa).toHaveBeenCalledTimes(1); 27 | expect(execa).toHaveBeenCalledWith('yarn', args, expect.any(Object)); 28 | expect(resp).toMatchObject(spawnResult); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/spawn/lib/node.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-unused-modules, @typescript-eslint/ban-types, @typescript-eslint/promise-function-async */ 2 | import spawn, { DefaultOptions, SpawnPromise } from './spawn'; 3 | 4 | /** 5 | * Spawns a node 6 | * 7 | * @param args the spawn arguments 8 | * @param options the spawn options 9 | */ 10 | // @ts-ignore 11 | const node = async (args: string[], options: object = DefaultOptions): SpawnPromise => spawn('node', args, options); 12 | 13 | export default node; 14 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/spawn/lib/npm.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-unused-modules, @typescript-eslint/ban-types, @typescript-eslint/promise-function-async */ 2 | import spawn, { DefaultOptions, SpawnPromise } from './spawn'; 3 | 4 | /** 5 | * Spawns an npm 6 | * 7 | * @param args the spawn arguments 8 | * @param options the spawn options 9 | */ 10 | // @ts-ignore 11 | const npm = async (args: string[], options: object = DefaultOptions): SpawnPromise => spawn('npm', args, options); 12 | 13 | export default npm; 14 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/spawn/lib/yarn.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-unused-modules, @typescript-eslint/ban-types, @typescript-eslint/promise-function-async */ 2 | import spawn, { SpawnPromise, DefaultOptions } from './spawn'; 3 | 4 | /** 5 | * Spawns a yarn 6 | * 7 | * @param args the spawn arguments 8 | * @param options the spawn options 9 | */ 10 | // @ts-ignore 11 | const yarn = async (args: string[], options: object = DefaultOptions): SpawnPromise => spawn('yarn', args, options); 12 | 13 | export default yarn; 14 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/telemetry/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Telemetry } from './lib/telemetry'; 2 | export { TRACK_EVENT_NAME, COMMAND_PREFIX } from './lib/constants'; 3 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/telemetry/lib/constants.ts: -------------------------------------------------------------------------------- 1 | export const DEV_VALUE: string = 'EWHZFRBOzhilIXGTHthXn6lnTU7GcLAn'; 2 | export const STAGE_VALUE: string = 'GxzxBFcPWubtLF1oH6RilRtOwgGUMD4v'; 3 | export const PROD_VALUE: string = 'HDEFDwSej4zudHHWj5ElRtZGPRu29rjY'; 4 | export const PRODUCT: string = 'Flex'; 5 | export const SOURCE: string = 'flexpluginscli'; 6 | export const TRACK_EVENT_NAME: string = 'PCLI Run'; 7 | export const COMMAND_PREFIX: string = 'flex:plugins:'; 8 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/telemetry/lib/track.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Script to call Segment Track method with the track payload 3 | * This is used when Segment APIs need to be called in a daemon process asynchronously 4 | */ 5 | 6 | import { track } from './telemetry'; 7 | 8 | track(JSON.parse(process.argv[2])); 9 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/src/updateNotifier.ts: -------------------------------------------------------------------------------- 1 | import updateNotifier, { NotifyOptions, Settings } from 'update-notifier'; 2 | import packageJson from 'package-json'; 3 | 4 | import { readPackageJson, findUp, readAppPackageJson } from './fs'; 5 | import { chalk } from '.'; 6 | 7 | export default updateNotifier; 8 | 9 | /** 10 | * Checks for update for the package 11 | */ 12 | /* c8 ignore next */ 13 | // eslint-disable-next-line import/no-unused-modules 14 | export const checkForUpdate = async ( 15 | settings: Partial = {}, 16 | customMessage: Partial = {}, 17 | ): Promise => { 18 | const pkg = module.parent ? readPackageJson(findUp(module.parent.filename, 'package.json')) : readAppPackageJson(); 19 | const pkgInfo = await packageJson(pkg.name, { version: pkg.version }); 20 | const notifier = updateNotifier({ pkg, updateCheckInterval: 1000, ...settings }); 21 | 22 | const message = `${ 23 | pkgInfo.deprecated 24 | ? `${chalk.bgYellow.bold('You are currently using a deprecated version of Flex Plugin CLI')}\n\n` 25 | : '' 26 | }Update available ${chalk.dim(notifier.update?.current)}${chalk.reset(' → ')}${chalk.green( 27 | notifier.update?.latest, 28 | )} \nRun ${chalk.cyan('twilio plugins:install @twilio-labs/plugin-flex')} to update`; 29 | 30 | notifier.notify({ message, ...customMessage }); 31 | }; 32 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "baseUrl": ".", 6 | "rootDir": "src", 7 | "esModuleInterop": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/flex-dev-utils/tsconfig.lint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": [ 4 | "node_modules", 5 | "dist" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /packages/flex-plugin-e2e-tests/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (C) 2018, Twilio, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /packages/flex-plugin-e2e-tests/jest.config.js: -------------------------------------------------------------------------------- 1 | const base = require('./../../jest.base.js'); 2 | const pkg = require('./package'); 3 | 4 | module.exports = { 5 | rootDir: '.', 6 | ...base(pkg), 7 | coverageThreshold: { 8 | global: { 9 | statements: 0, 10 | branches: 0, 11 | lines: 0, 12 | functions: 0, 13 | }, 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /packages/flex-plugin-e2e-tests/src/core/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-unused-modules */ 2 | export { PluginType, TestParams, TestScenario, testScenarios, testParams, homeDir } from './parameters'; 3 | export { TestSuite, testSuites } from './suites'; 4 | export { default as runner } from './runner'; 5 | -------------------------------------------------------------------------------- /packages/flex-plugin-e2e-tests/src/core/suites.ts: -------------------------------------------------------------------------------- 1 | import { readdirSync } from 'fs'; 2 | 3 | import { TestParams } from './parameters'; 4 | 5 | export interface TestSuite { 6 | (params: TestParams): Promise; 7 | description: string; 8 | before?: (params: TestParams) => Promise; 9 | after?: (params: TestParams) => Promise; 10 | } 11 | 12 | /** 13 | * All the test suites that need to run 14 | */ 15 | export const testSuites = readdirSync(`${__dirname}/../tests`) 16 | .filter((f) => f.endsWith('.js')) 17 | .filter((f) => f.startsWith('step')) 18 | .sort((l, r) => { 19 | if (parseInt(l.split('step')[1], 10) > parseInt(r.split('step')[1], 10)) { 20 | return 1; 21 | } 22 | 23 | return -1; 24 | }); 25 | -------------------------------------------------------------------------------- /packages/flex-plugin-e2e-tests/src/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires, @typescript-eslint/prefer-for-of, global-require */ 2 | import { logger } from '@twilio/flex-dev-utils'; 3 | 4 | import { runner, testParams, testScenarios } from './core'; 5 | 6 | runner(testParams, testScenarios) 7 | .then(() => { 8 | logger.success('All E2E tests passed successfully'); 9 | }) 10 | .catch((e) => { 11 | logger.error('Failed to run E2E tests'); 12 | logger.info(e); 13 | 14 | // eslint-disable-next-line no-process-exit 15 | process.exit(1); 16 | }); 17 | -------------------------------------------------------------------------------- /packages/flex-plugin-e2e-tests/src/tests/step001.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-unused-modules */ 2 | import { TestSuite, TestParams } from '../core'; 3 | import { spawn } from '../utils'; 4 | 5 | // Install Twilio CLI and Plugins CLI 6 | const testSuite: TestSuite = async ({ environment, scenario }: TestParams): Promise => { 7 | const cliInstallArgs = ['install', '-g', 'twilio-cli']; 8 | 9 | if (!environment.ignorePrefix) { 10 | cliInstallArgs.push(`--prefix=${environment.homeDir}`); 11 | } 12 | 13 | await spawn('npm', cliInstallArgs); 14 | 15 | await spawn('twilio', ['plugins:install', `@twilio-labs/plugin-flex@${scenario.packageVersion}`]); 16 | }; 17 | testSuite.description = 'Installing Twilio CLI and Plugins CLI'; 18 | 19 | export default testSuite; 20 | -------------------------------------------------------------------------------- /packages/flex-plugin-e2e-tests/src/tests/step003.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-unused-modules */ 2 | 3 | import { TestSuite, TestParams } from '../core'; 4 | import { spawn, assertion, pluginHelper } from '../utils'; 5 | 6 | // Install dependencies 7 | const testSuite: TestSuite = async ({ scenario }: TestParams): Promise => { 8 | const plugin = scenario.plugins[0]; 9 | assertion.not.isNull(plugin); 10 | 11 | pluginHelper.changeFlexUIVersionIfRequired(scenario, plugin); 12 | 13 | // await spawn('npm', ['i'], { cwd: plugin.dir }); 14 | 15 | assertion.fileExists([plugin.dir, 'node_modules']); 16 | assertion.fileExists([plugin.dir, 'node_modules', '@twilio/flex-plugin-scripts']); 17 | assertion.jsonFileContains( 18 | [plugin.dir, 'node_modules', '@twilio/flex-plugin-scripts', 'package.json'], 19 | 'version', 20 | scenario.packageVersion, 21 | ); 22 | }; 23 | testSuite.description = 'Running {{npm i}}'; 24 | 25 | export default testSuite; 26 | -------------------------------------------------------------------------------- /packages/flex-plugin-e2e-tests/src/tests/step004.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-unused-modules */ 2 | import { logger } from '@twilio/flex-dev-utils'; 3 | 4 | import { TestSuite, TestParams } from '../core'; 5 | import { spawn } from '../utils'; 6 | 7 | // Run plugin tests 8 | const testSuite: TestSuite = async ({ scenario, environment }: TestParams): Promise => { 9 | // To be addressed in future PR 10 | if (environment.operatingSystem === 'win32') { 11 | logger.warning('Skipping [flex:plugins:test] on Win32'); 12 | } else { 13 | await spawn('twilio', ['flex:plugins:test', '-l', 'debug'], { 14 | cwd: scenario.plugins[0].dir, 15 | }); 16 | } 17 | }; 18 | testSuite.description = 'Running {{twilio flex:plugins:test}}'; 19 | 20 | export default testSuite; 21 | -------------------------------------------------------------------------------- /packages/flex-plugin-e2e-tests/src/tests/step005.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-unused-modules */ 2 | 3 | import { replaceInFile } from 'replace-in-file'; 4 | 5 | import { TestSuite, TestParams } from '../core'; 6 | import { spawn, assertion, joinPath } from '../utils'; 7 | 8 | // Build plugin 9 | const testSuite: TestSuite = async ({ scenario }: TestParams): Promise => { 10 | const plugin = scenario.plugins[0]; 11 | assertion.not.isNull(plugin); 12 | 13 | const ext = scenario.isTS ? 'tsx' : 'jsx'; 14 | await replaceInFile({ 15 | files: joinPath(plugin.dir, 'src', 'components', 'CustomTaskList', `CustomTaskList.${ext}`), 16 | from: /This is a dismissible demo component./, 17 | to: plugin.componentText, 18 | }); 19 | 20 | await spawn('twilio', ['flex:plugins:build', '-l', 'debug'], { cwd: plugin.dir }); 21 | 22 | assertion.not.dirIsEmpty([plugin.dir, 'build']); 23 | assertion.fileExists([plugin.dir, 'build', `${plugin.name}.js`]); 24 | assertion.fileExists([plugin.dir, 'build', `${plugin.name}.js.map`]); 25 | assertion.fileContains([plugin.dir, 'build', `${plugin.name}.js`], plugin.componentText); 26 | }; 27 | testSuite.description = 'Running {{twilio flex:plugins:build}}'; 28 | 29 | export default testSuite; 30 | -------------------------------------------------------------------------------- /packages/flex-plugin-e2e-tests/src/tests/step009.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-unused-modules, @typescript-eslint/no-non-null-assertion */ 2 | import { assertion, spawn, api, sleep } from '../utils'; 3 | import { TestSuite, TestParams } from '../core'; 4 | 5 | // Release plugin 6 | const testSuite: TestSuite = async ({ scenario, config }: TestParams): Promise => { 7 | const plugin = scenario.plugins[0]; 8 | assertion.not.isNull(plugin); 9 | 10 | const result = await spawn('twilio', [ 11 | 'flex:plugins:release', 12 | '--plugin', 13 | `${plugin.name}@${plugin.version}`, 14 | '-l', 15 | 'debug', 16 | ...config.regionFlag, 17 | ]); 18 | await sleep(1000); 19 | 20 | const release = await api.getActiveRelease(); 21 | const plugins = await api.getActivePlugins(release!.configuration_sid); 22 | 23 | assertion.stringContains(result.stdout, 'successful'); 24 | assertion.stringContains(result.stdout, 'Configuration FJ'); 25 | assertion.stringContains(result.stdout, 'enabled'); 26 | assertion.equal(1, plugins.plugins.length); 27 | assertion.equal(plugins.plugins[0].unique_name, plugin.name); 28 | assertion.equal(plugins.plugins[0].version, plugin.version); 29 | }; 30 | testSuite.description = 'Running {{twilio flex:plugins:release}}'; 31 | 32 | export default testSuite; 33 | -------------------------------------------------------------------------------- /packages/flex-plugin-e2e-tests/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-unused-modules */ 2 | 3 | export { promisifiedSpawn as spawn, logResult, killChildProcess, retryOnError } from './spawn'; 4 | export { default as assertion } from './assertion'; 5 | export { writeFileSync } from 'fs'; 6 | export { default as api } from './plugins-api'; 7 | export { default as pluginHelper } from './plugin-helper'; 8 | export * from './browser'; 9 | export * from './timers'; 10 | export { join as joinPath } from 'path'; 11 | -------------------------------------------------------------------------------- /packages/flex-plugin-e2e-tests/src/utils/pages/index.ts: -------------------------------------------------------------------------------- 1 | export * from './app'; 2 | -------------------------------------------------------------------------------- /packages/flex-plugin-e2e-tests/src/utils/pages/view/admin-dashboard.ts: -------------------------------------------------------------------------------- 1 | import { ElementHandle, Page } from 'puppeteer'; 2 | 3 | import { Base } from './base'; 4 | 5 | export class AdminDashboard extends Base { 6 | private static readonly _adminDashboardSubHeader = '[data-testid="admin-subheader"]'; 7 | 8 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types 9 | assert = { 10 | /** 11 | * Checks whether Welcome Banner is visible on Admin Dashboard 12 | */ 13 | isVisible: async (): Promise> => 14 | this.elementVisible(AdminDashboard._adminDashboardSubHeader, 'Admin Dashbaord Sub-Header'), 15 | }; 16 | 17 | private readonly _baseUrl: string; 18 | 19 | constructor(page: Page, baseUrl: string) { 20 | super(page); 21 | this._baseUrl = baseUrl; 22 | } 23 | 24 | /** 25 | * Navigates to Admin Dashboard 26 | */ 27 | async open(): Promise { 28 | await this.goto({ baseUrl: this._baseUrl, path: 'admin' }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/flex-plugin-e2e-tests/src/utils/pages/view/agent-desktop.ts: -------------------------------------------------------------------------------- 1 | import { ElementHandle, Page } from 'puppeteer'; 2 | 3 | import { Base } from './base'; 4 | 5 | export class AgentDesktop extends Base { 6 | private static readonly _noTaskCanvas = '.Twilio-NoTasksCanvas'; 7 | 8 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types 9 | assert = { 10 | /** 11 | * Checks whether task canvas are visible on the Agent Desktop 12 | */ 13 | isVisible: async (): Promise> => 14 | this.elementVisible(AgentDesktop._noTaskCanvas, 'Task canvas'), 15 | }; 16 | 17 | private readonly _baseUrl: string; 18 | 19 | constructor(page: Page, baseUrl: string) { 20 | super(page); 21 | this._baseUrl = baseUrl; 22 | } 23 | 24 | /** 25 | * Navigates to Agent Desktop 26 | */ 27 | async open(): Promise { 28 | await this.goto({ baseUrl: this._baseUrl, path: 'agent-desktop' }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/flex-plugin-e2e-tests/src/utils/serverless-api.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | import { testParams } from '../core'; 4 | 5 | interface Service { 6 | // eslint-disable-next-line camelcase 7 | unique_name: string; 8 | sid: string; 9 | } 10 | 11 | const realm = testParams.config.region ? `${testParams.config.region}.` : ''; 12 | const baseUrl = `https://serverless.${realm}twilio.com/v1`; 13 | const auth = { 14 | username: testParams.secrets.api.accountSid, 15 | password: testParams.secrets.api.authToken, 16 | }; 17 | 18 | const get = async (uri: string) => { 19 | return axios.get(`${baseUrl}/${uri}`, { auth }).then((resp) => resp.data); 20 | }; 21 | 22 | const remove = async (uri: string) => { 23 | return axios.delete(`${baseUrl}/${uri}`, { auth }); 24 | }; 25 | 26 | export const getServiceSid = async (): Promise => { 27 | return get('Services').then((list) => list.services.find((service: Service) => service.unique_name === 'default')); 28 | }; 29 | 30 | export const deleteEnvironments = async (serviceSid: string): Promise => { 31 | const list = await get(`Services/${serviceSid}/Environments`); 32 | for (const environment of list.environments) { 33 | await remove(`Services/${serviceSid}/Environments/${environment.sid}`); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /packages/flex-plugin-e2e-tests/src/utils/timers.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Sleeps for the given time 3 | * @param ms time to sleep in milliseconds 4 | */ 5 | export const sleep = async (ms: number): Promise => new Promise((resolve) => setTimeout(resolve, ms)); 6 | -------------------------------------------------------------------------------- /packages/flex-plugin-e2e-tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "baseUrl": ".", 6 | "rootDir": "src", 7 | "esModuleInterop": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/flex-plugin-e2e-tests/tsconfig.lint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": [ 4 | "node_modules", 5 | "dist" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (C) 2018, Twilio, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/__mocks__/ora.ts: -------------------------------------------------------------------------------- 1 | module.exports = () => ({ 2 | start: () => { 3 | // no-op 4 | }, 5 | fail: () => { 6 | // no-op 7 | }, 8 | succeed: () => { 9 | // no-op 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/bin/flex-plugin: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | require = require('esm-wallaby')(module /*, options*/); 6 | require('../dist').default(...process.argv.splice(2)); 7 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/dev_assets/flex-shim.js: -------------------------------------------------------------------------------- 1 | let flexInstance = window.Twilio.Flex; 2 | if (window.Twilio && window.Twilio.FlexProxy && window.Twilio.FlexProxy[__FPB_PLUGIN_UNIQUE_NAME]) { 3 | flexInstance = window.Twilio.FlexProxy[__FPB_PLUGIN_UNIQUE_NAME]; 4 | } 5 | module.exports = flexInstance; 6 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/dev_assets/flex.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700'); 2 | 3 | body, #container, .Twilio-ContactCenter-Container { 4 | font-family: 'Open Sans', sans-serif; 5 | width: 100vw; 6 | height: 100vh; 7 | margin: 0px; 8 | display: flex; 9 | } 10 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/dev_assets/flex.js: -------------------------------------------------------------------------------- 1 | (function(global, undefined) { 2 | 'use strict'; 3 | global.loadFlex = function loadFlex(targetEl) { 4 | Twilio.Flex.runDefault(appConfig, targetEl).catch(() => { 5 | throw new Error('Failed to authenticate user.'); 6 | }); 7 | }; 8 | })(typeof window !== 'undefined' ? window : this); 9 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/dev_assets/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Twilio Flex 12 | 13 | 14 | 15 | 16 | 17 | 18 | %__FPB_JS_SCRIPTS% 19 | 20 | 21 | 22 | 23 |
24 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/dev_assets/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "src", 4 | "rootDir": ".", 5 | "outDir": "build", 6 | "target": "es5", 7 | "lib": [ 8 | "dom", 9 | "dom.iterable", 10 | "esnext" 11 | ], 12 | "allowJs": true, 13 | "skipLibCheck": true, 14 | "esModuleInterop": true, 15 | "allowSyntheticDefaultImports": true, 16 | "strict": true, 17 | "forceConsistentCasingInFileNames": true, 18 | "module": "esnext", 19 | "moduleResolution": "node", 20 | "resolveJsonModule": true, 21 | "isolatedModules": true, 22 | "noEmit": true, 23 | "jsx": "preserve", 24 | "noUnusedLocals": false, 25 | "noUnusedParameters": false 26 | }, 27 | "include": [ 28 | "./src/**/*" 29 | ], 30 | "exclude": [ 31 | "./**/*.test.ts", 32 | "./**/*.test.tsx", 33 | "./**/__mocks__/*.ts", 34 | "./**/__mocks__/*.tsx" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/docs.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | main: './README.md', 3 | output: './README.md', 4 | }; 5 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/jest.config.js: -------------------------------------------------------------------------------- 1 | const base = require('./../../jest.base.js'); 2 | const pkg = require('./package'); 3 | 4 | module.exports = { 5 | rootDir: '.', 6 | ...base(pkg), 7 | coverageThreshold: { 8 | global: { 9 | statements: 94, 10 | branches: 88, 11 | lines: 94, 12 | functions: 92, 13 | }, 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/src/clients/__tests__/accounts.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase */ 2 | import { Credential } from '@twilio/flex-dev-utils'; 3 | 4 | import AccountClient from '../accounts'; 5 | 6 | describe('AccountClient', () => { 7 | const auth: Credential = { 8 | username: 'AC00000000000000000000000000000000', 9 | password: 'abc', 10 | }; 11 | 12 | describe('get', () => { 13 | it('should get configuration', async () => { 14 | const client = new AccountClient(auth.username, auth.password); 15 | const account = { 16 | account_sid: auth.username, 17 | friendly_name: 'test-account', 18 | }; 19 | // @ts-ignore 20 | const get = jest.spyOn(client.http, 'get').mockResolvedValue(account); 21 | 22 | const result = await client.get(auth.username); 23 | 24 | expect(get).toHaveBeenCalledTimes(1); 25 | expect(get).toHaveBeenCalledWith(expect.stringContaining(auth.username)); 26 | expect(get).toHaveBeenCalledWith(expect.stringContaining('.json')); 27 | expect(result).toEqual(account); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/src/clients/__tests__/assets.test.ts: -------------------------------------------------------------------------------- 1 | import { Credential } from '@twilio/flex-dev-utils'; 2 | 3 | import AssetClient from '../assets'; 4 | import ServerlessClient from '../serverless-client'; 5 | import FileClient from '../files'; 6 | 7 | jest.mock('../files'); 8 | 9 | describe('AssetClient', () => { 10 | const serviceSid = 'ZS00000000000000000000000000000000'; 11 | const auth = {} as Credential; 12 | 13 | describe('constructor', () => { 14 | it('should instantiate as an Asset', () => { 15 | const baseClient = new ServerlessClient(auth.username, auth.password); 16 | const client = new AssetClient(baseClient, serviceSid); 17 | expect(FileClient).toHaveBeenCalledTimes(1); 18 | expect(FileClient).toHaveBeenCalledWith(baseClient, 'Assets', serviceSid); 19 | expect(client).toBeInstanceOf(FileClient); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/src/clients/accounts.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase */ 2 | import { HttpClient } from '@twilio/flex-dev-utils'; 3 | 4 | export interface Account { 5 | auth_token?: string; 6 | friendly_name?: string; 7 | sid: string; 8 | } 9 | 10 | export default class AccountClient { 11 | public static version = '2010-04-01'; 12 | 13 | private readonly http: HttpClient; 14 | 15 | constructor(username: string, password: string) { 16 | this.http = new HttpClient({ 17 | baseURL: `https://api.twilio.com/${AccountClient.version}`, 18 | auth: { username, password }, 19 | supportProxy: true, 20 | }); 21 | } 22 | 23 | /** 24 | * Returns the Account object 25 | * 26 | * @param sid the account sid to lookup 27 | */ 28 | public get = async (sid: string): Promise => { 29 | return this.http.get(`Accounts/${sid}.json`); 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/src/clients/assets.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase */ 2 | 3 | import ServerlessClient from './serverless-client'; 4 | import FileClient from './files'; 5 | 6 | export enum Visibility { 7 | Public = 'public', 8 | Protected = 'protected', 9 | } 10 | 11 | export interface ServerlessFileVersion { 12 | sid: string; 13 | account_sid: string; 14 | url: string; 15 | date_updated: string; 16 | date_created: string; 17 | visibility: Visibility; 18 | service_sid: string; 19 | path: string; 20 | } 21 | 22 | export interface FunctionVersion extends ServerlessFileVersion { 23 | function_sid: string; 24 | } 25 | 26 | export interface AssetVersion extends ServerlessFileVersion { 27 | asset_sid: string; 28 | } 29 | 30 | export default class AssetClient extends FileClient { 31 | constructor(client: ServerlessClient, serviceSid: string) { 32 | super(client, 'Assets', serviceSid); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/src/clients/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase, import/no-unused-modules */ 2 | 3 | import { ServerlessService } from './services'; 4 | import { ServerlessEnvironment } from './environments'; 5 | import { ServerlessBuild } from './builds'; 6 | 7 | export interface ServerlessRuntime { 8 | service: ServerlessService; 9 | environment?: ServerlessEnvironment; 10 | build?: ServerlessBuild; 11 | } 12 | 13 | export { ServerlessFile, FileVisibility } from './files'; 14 | export { default as AccountsClient } from './accounts'; 15 | export { default as AssetClient, AssetVersion, FunctionVersion, Visibility, ServerlessFileVersion } from './assets'; 16 | export { default as ServiceClient, ServerlessService } from './services'; 17 | export { default as EnvironmentClient, ServerlessEnvironment } from './environments'; 18 | export { default as BuildClient, ServerlessBuild, BuildStatus } from './builds'; 19 | export { default as DeploymentClient } from './deployments'; 20 | export { default as ConfigurationClient, UIDependencies } from './configurations'; 21 | export { default as ServerlessClient } from './serverless-client'; 22 | export { default as GovernorClient } from './governor'; 23 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/src/clients/serverless-client.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient, OptionalHttpClientConfig } from '@twilio/flex-dev-utils'; 2 | 3 | /** 4 | * An implementation of the raw {@link HttpClient} but made for Serverless services 5 | */ 6 | export default class ServerlessClient extends HttpClient { 7 | private static version = 'v1'; 8 | 9 | constructor(username: string, password: string, options?: OptionalHttpClientConfig) { 10 | super({ 11 | ...options, 12 | baseURL: `https://serverless.twilio.com/${ServerlessClient.version}`, 13 | auth: { username, password }, 14 | supportProxy: true, 15 | }); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/src/clients/serverless-types.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/flex-plugin-builder/b66bb457f4181ffe605af31c319bde5b14303012/packages/flex-plugin-scripts/src/clients/serverless-types.ts -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/src/config/jest.config.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | 3 | import { InitialOptions } from '@jest/types/build/Config'; 4 | import { getPaths } from '@twilio/flex-dev-utils/dist/fs'; 5 | 6 | export { JestConfigurations } from '@twilio/flex-plugin-test'; 7 | 8 | /** 9 | * Main method for generating a default Jest configuration 10 | */ 11 | export default (): Partial => { 12 | return { 13 | rootDir: getPaths().cwd, 14 | preset: join(require.resolve('@twilio/flex-plugin-test'), '..', '..'), 15 | }; 16 | }; 17 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/src/module.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'pnp-webpack-plugin'; 2 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/src/prints/appConfigMissing.ts: -------------------------------------------------------------------------------- 1 | import { logger, env, singleLineString } from '@twilio/flex-dev-utils'; 2 | 3 | const { link } = logger.coloredStrings; 4 | 5 | /** 6 | * Error about appConfig.js missing 7 | */ 8 | export default (): void => { 9 | const nameColor = logger.coloredStrings.name; 10 | const { headline } = logger.coloredStrings; 11 | 12 | const scriptName = nameColor('@twilio/flex-plugin-scripts'); 13 | 14 | env.setQuiet(false); 15 | logger.newline(); 16 | logger.error('There might be a problem with your project file hierarchy.'); 17 | logger.newline(); 18 | 19 | logger.info(`The ${scriptName} requires the following file to be present:`); 20 | logger.newline(); 21 | logger.info(`\t ${headline('public/appConfig.js')}`); 22 | logger.newline(); 23 | 24 | logger.info( 25 | singleLineString( 26 | `Check your ${link('public/')} directory for ${link('appConfig.example.js')},`, 27 | `copy it to ${link('appConfig.js')}, and modify your Account Sid and Service URL.`, 28 | ), 29 | ); 30 | 31 | logger.newline(); 32 | env.setQuiet(true); 33 | }; 34 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/src/prints/availabilityWarning.ts: -------------------------------------------------------------------------------- 1 | import { boxen, env } from '@twilio/flex-dev-utils'; 2 | 3 | /** 4 | * Prints a warning message about the availability of the release script 5 | */ 6 | export default (): void => { 7 | env.setQuiet(false); 8 | boxen.warning('Release script is currently in pilot and is limited in availability'); 9 | env.setQuiet(true); 10 | }; 11 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/src/prints/deploySuccessful.ts: -------------------------------------------------------------------------------- 1 | import { logger, singleLineString } from '@twilio/flex-dev-utils'; 2 | 3 | import { Account } from '../clients/accounts'; 4 | 5 | /** 6 | * Successful message to print after a deploy 7 | * 8 | * @param url the Asset URL 9 | * @param isPublic whether the Asset is uploaded publicly or privately 10 | * @param account the account doing the deploy 11 | */ 12 | export default (url: string, isPublic: boolean, account: Account): void => { 13 | const availability = isPublic ? 'publicly' : 'privately'; 14 | const nameLogger = logger.coloredStrings.name; 15 | const friendlyName = account.friendly_name || account.sid; 16 | const accountSid = (friendlyName !== account.sid && ` (${nameLogger(account.sid)})`) || ''; 17 | 18 | logger.newline(); 19 | logger.success( 20 | singleLineString( 21 | '🚀 Your plugin has been successfully deployed to your Flex project', 22 | `${nameLogger(friendlyName)}${accountSid}.`, 23 | `It is hosted (${availability}) as a Twilio Asset on ${logger.coloredStrings.link(url)}.`, 24 | ), 25 | ); 26 | logger.newline(); 27 | }; 28 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/src/prints/expectedDependencyNotFound.ts: -------------------------------------------------------------------------------- 1 | import { logger, env } from '@twilio/flex-dev-utils'; 2 | 3 | import instructionToReinstall from './instructionToReinstall'; 4 | 5 | /** 6 | * An expected dependency from flex-ui package.json is missing. 7 | * 8 | * @param packageName the package name 9 | */ 10 | export default (packageName: string): void => { 11 | const nameColor = logger.coloredStrings.name; 12 | const flexUIName = nameColor('@twilio/flex-ui'); 13 | 14 | env.setQuiet(false); 15 | logger.newline(); 16 | logger.error('An expected package was not found.'); 17 | logger.newline(); 18 | 19 | logger.info(`Expected package ${nameColor(packageName)} was not found in ${flexUIName}.`); 20 | logger.newline(); 21 | 22 | instructionToReinstall(); 23 | env.setQuiet(true); 24 | }; 25 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/src/prints/fileTooLarge.ts: -------------------------------------------------------------------------------- 1 | import { env, logger } from '@twilio/flex-dev-utils'; 2 | import { printList } from '@twilio/flex-dev-utils/dist/prints'; 3 | 4 | export default (type: 'bundle' | 'sourcemap', size: number, max: number): void => { 5 | env.setQuiet(false); 6 | 7 | // Round to 1 decimal 8 | size = Math.round(size * 10) / 10; 9 | 10 | logger.newline(); 11 | logger.info(`--Plugin ${type} size **${size}MB** exceeds allowed limit of **${max}MB**.--`); 12 | logger.newline(); 13 | logger.info('Consider the following optimizations:'); 14 | 15 | const lines = [ 16 | 'Host your image files on Twilio Assets instead of bundling them with your plugin', 17 | 'Use SVG instead of PNG/JPEG/GIF image formats', 18 | ]; 19 | printList(...lines); 20 | 21 | env.setQuiet(true); 22 | }; 23 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/src/prints/index.ts: -------------------------------------------------------------------------------- 1 | export { default as appConfigMissing } from './appConfigMissing'; 2 | export { default as availabilityWarning } from './availabilityWarning'; 3 | export { default as pluginVersions } from './pluginVersions'; 4 | export { default as expectedDependencyNotFound } from './expectedDependencyNotFound'; 5 | export { default as deploySuccessful } from './deploySuccessful'; 6 | export { default as packagesVersions } from './packagesVersions'; 7 | export { default as buildSuccessful } from './buildSuccessful'; 8 | export { default as buildFailure } from './buildFailure'; 9 | export { default as typescriptNotInstalled } from './typescriptNotInstalled'; 10 | export { default as jestNotInstalled } from './jestNotInstalled'; 11 | export { default as unbundledReactMismatch } from './unbundledReactMismatch'; 12 | export { default as loadPluginCountError } from './loadPluginCountError'; 13 | export { default as serverCrashed } from './serverCrashed'; 14 | export { default as fileTooLarge } from './fileTooLarge'; 15 | export { default as localReactIncompatibleWithRemote } from './localReactIncompatibleWithRemote'; 16 | export { default as validateSuccessful } from './validateSuccessful'; 17 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/src/prints/instructionToReinstall.ts: -------------------------------------------------------------------------------- 1 | import { logger, env } from '@twilio/flex-dev-utils'; 2 | import { printList } from '@twilio/flex-dev-utils/dist/prints'; 3 | 4 | /** 5 | * Instructions for removing node_modules and lock files and re-installing 6 | * 7 | * @param extras any extra steps to include 8 | */ 9 | export default (...extras: string[]): void => { 10 | const nameColor = logger.coloredStrings.name; 11 | const { headline } = logger.coloredStrings; 12 | 13 | env.setQuiet(false); 14 | logger.info(`Please follow these steps to possibly ${headline('fix')} this issue:`); 15 | const lines = [ 16 | `Delete your ${nameColor('node_modules')} directory`, 17 | `Delete ${nameColor('package-lock.json')} and/or ${nameColor('yarn.lock')}`, 18 | ...extras, 19 | `Run ${nameColor('npm install')} or ${nameColor('yarn install')} again`, 20 | ]; 21 | 22 | printList(...lines); 23 | env.setQuiet(true); 24 | }; 25 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/src/prints/jestNotInstalled.ts: -------------------------------------------------------------------------------- 1 | import { logger, env } from '@twilio/flex-dev-utils'; 2 | 3 | /** 4 | * Prints an error if jest module is not installed 5 | */ 6 | export default (): void => { 7 | const bold = logger.colors.bold('jest'); 8 | 9 | env.setQuiet(false); 10 | logger.error("It looks like you're trying to use Jest but do not have the %s package installed.", bold); 11 | logger.info('Please install %s by running:', bold); 12 | logger.installInfo('npm', 'install jest --save-dev'); 13 | logger.newline(); 14 | env.setQuiet(true); 15 | }; 16 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/src/prints/loadPluginCountError.ts: -------------------------------------------------------------------------------- 1 | import { logger, env, singleLineString } from '@twilio/flex-dev-utils'; 2 | 3 | export default (count: number): void => { 4 | const { bold } = logger.colors; 5 | const { link } = logger.coloredStrings; 6 | 7 | env.setQuiet(false); 8 | logger.error('There must be one and only one plugin loaded in each Flex plugin.'); 9 | logger.newline(); 10 | logger.info( 11 | singleLineString( 12 | `There are ${bold(count.toString())} plugin(s) being loaded in this source code.`, 13 | `Check that the ${bold('src/index')} file contains only one call to`, 14 | `${bold('FlexPlugin.loadPlugin(...)')}.`, 15 | `For more information, please refer to ${link('https://www.twilio.com/docs/flex/plugins')}.`, 16 | ), 17 | ); 18 | logger.newline(); 19 | env.setQuiet(true); 20 | }; 21 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/src/prints/localReactIncompatibleWithRemote.ts: -------------------------------------------------------------------------------- 1 | import { logger, singleLineString } from '@twilio/flex-dev-utils'; 2 | 3 | export default (localVersion: string, remoteVersion: string): void => { 4 | logger.newline(); 5 | logger.warning( 6 | singleLineString( 7 | `The React version ${localVersion} installed locally`, 8 | `is incompatible with the React version ${remoteVersion} installed on your Flex project.`, 9 | ), 10 | ); 11 | logger.info( 12 | singleLineString( 13 | 'Change your local React version or visit https://flex.twilio.com/admin/developers to', 14 | `change the React version installed on your Flex project.`, 15 | ), 16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/src/prints/packagesVersions.ts: -------------------------------------------------------------------------------- 1 | import { logger } from '@twilio/flex-dev-utils'; 2 | 3 | import { PackageDetail } from '../utils/package'; 4 | import instructionToReinstall from './instructionToReinstall'; 5 | 6 | export default (foundPackages: PackageDetail[], notFoundPackages: PackageDetail[]): void => { 7 | const { headline } = logger.coloredStrings; 8 | 9 | logger.info('Your plugin has the following packages installed:'); 10 | logger.newline(); 11 | foundPackages.forEach((detail) => { 12 | logger.info(`\t ${headline(`"${detail.name}": "${detail.package.version}"`)}`); 13 | }); 14 | 15 | if (notFoundPackages.length) { 16 | logger.newline(); 17 | logger.error('However, some required packages were not found:'); 18 | logger.newline(); 19 | notFoundPackages.forEach((detail) => { 20 | logger.info(`\t ${headline(`"${detail.name}"`)}`); 21 | }); 22 | logger.newline(); 23 | 24 | instructionToReinstall(); 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/src/prints/preFlightByPass.ts: -------------------------------------------------------------------------------- 1 | import { logger, env, multilineString } from '@twilio/flex-dev-utils'; 2 | 3 | /** 4 | * Prints the SKIP_PREFLIGHT_CHECK message 5 | * 6 | * @param skip whether SKIP_PREFLIGHT_CHECK is already set 7 | */ 8 | export default (skip: boolean): void => { 9 | env.setQuiet(false); 10 | if (skip) { 11 | logger.warning('SKIP_PREFLIGHT_CHECK=true is used and the warning is ignored; your script will continue.'); 12 | } else { 13 | logger.warning( 14 | multilineString( 15 | 'If you like to skip this and proceed anyway, use SKIP_PREFLIGHT_CHECK=true environment variable.', 16 | 'This will disable checks and allow you to run your application.', 17 | ), 18 | ); 19 | } 20 | env.setQuiet(true); 21 | }; 22 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/src/prints/serverCrashed.ts: -------------------------------------------------------------------------------- 1 | import { OnDevServerCrashedPayload } from '@twilio/flex-plugin-webpack'; 2 | import { logger } from '@twilio/flex-dev-utils'; 3 | 4 | export default (payload: OnDevServerCrashedPayload): void => { 5 | logger.error('Flex Plugin Builder server has crashed:', payload.exception.message); 6 | logger.info(payload.exception.stack); 7 | }; 8 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/src/prints/typescriptNotInstalled.ts: -------------------------------------------------------------------------------- 1 | import { logger, env } from '@twilio/flex-dev-utils'; 2 | 3 | /** 4 | * Prints an error if typescript module is not installed 5 | */ 6 | export default (): void => { 7 | const bold = logger.colors.bold('typescript'); 8 | 9 | env.setQuiet(false); 10 | logger.error("It looks like you're trying to use Typescript but do not have the %s package installed.", bold); 11 | logger.info('Please install %s by running:', bold); 12 | logger.installInfo('npm', 'install typescript --save-dev'); 13 | logger.newline(); 14 | env.setQuiet(true); 15 | }; 16 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/src/scripts/__tests__/clear.test.ts: -------------------------------------------------------------------------------- 1 | import * as credentials from '@twilio/flex-dev-utils/dist/credentials'; 2 | 3 | import * as clearScript from '../clear'; 4 | 5 | jest.mock('@twilio/flex-dev-utils/dist/logger/lib/logger'); 6 | 7 | describe('clear', () => { 8 | it('should clear credentials', async () => { 9 | const clearCredentials = jest.spyOn(credentials, 'clearCredentials').mockResolvedValue(); 10 | 11 | await clearScript.default(); 12 | 13 | expect(clearCredentials).toHaveBeenCalledTimes(1); 14 | 15 | clearCredentials.mockRestore(); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/src/scripts/__tests__/info.test.ts: -------------------------------------------------------------------------------- 1 | import * as packageUtil from '../../utils/package'; 2 | import * as infoScript from '../info'; 3 | import * as prints from '../../prints'; 4 | 5 | jest.mock('../../prints/packagesVersions'); 6 | 7 | jest.mock('@twilio/flex-dev-utils/dist/logger/lib/logger'); 8 | jest.mock('../../prints/packagesVersions'); 9 | 10 | describe('info', () => { 11 | beforeEach(() => { 12 | jest.resetAllMocks(); 13 | jest.resetModules(); 14 | }); 15 | 16 | const foundPackage: packageUtil.PackageDetail = { 17 | name: 'found-package', 18 | found: true, 19 | package: { 20 | name: 'found-package', 21 | version: '1.2.3', 22 | }, 23 | }; 24 | 25 | const notFoundPackage: packageUtil.PackageDetail = { 26 | name: 'not-found-package', 27 | found: false, 28 | package: {}, 29 | }; 30 | 31 | describe('default', () => { 32 | it('should get package details and print them', async () => { 33 | const getPackageDetails = jest 34 | .spyOn(packageUtil, 'getPackageDetails') 35 | .mockReturnValue([foundPackage, notFoundPackage]); 36 | 37 | await infoScript.default(); 38 | 39 | expect(getPackageDetails).toHaveBeenCalledTimes(1); 40 | expect(prints.packagesVersions).toHaveBeenCalledTimes(1); 41 | expect(prints.packagesVersions).toHaveBeenCalledWith([foundPackage], [notFoundPackage]); 42 | }); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/src/scripts/__tests__/pre-localrun-check.test.ts: -------------------------------------------------------------------------------- 1 | import * as fsScripts from '@twilio/flex-dev-utils/dist/fs'; 2 | 3 | import * as preLocalRunCheck from '../pre-localrun-check'; 4 | 5 | describe('PreLocalRunCheck', () => { 6 | beforeEach(() => { 7 | jest.resetAllMocks(); 8 | jest.resetModules(); 9 | }); 10 | 11 | describe('main', () => { 12 | const _checkRunPluginConfigurationExists = jest.spyOn(fsScripts, 'checkRunPluginConfigurationExists'); 13 | 14 | beforeEach(() => { 15 | _checkRunPluginConfigurationExists.mockReset(); 16 | 17 | _checkRunPluginConfigurationExists.mockReturnThis(); 18 | }); 19 | 20 | afterAll(() => { 21 | _checkRunPluginConfigurationExists.mockRestore(); 22 | }); 23 | 24 | it('should call all methods', async () => { 25 | await preLocalRunCheck.default(); 26 | 27 | expect(_checkRunPluginConfigurationExists).toHaveBeenCalledTimes(1); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/src/scripts/clear.ts: -------------------------------------------------------------------------------- 1 | import { logger, progress } from '@twilio/flex-dev-utils'; 2 | import { clearCredentials } from '@twilio/flex-dev-utils/dist/credentials'; 3 | 4 | import run from '../utils/run'; 5 | 6 | /** 7 | * Clears the environment 8 | */ 9 | const clear = async (): Promise => { 10 | logger.info('Clearing caches and stored credentials'); 11 | 12 | await progress('Removing stored credentials', async () => clearCredentials()); 13 | 14 | logger.newline(); 15 | logger.info('✨ Successfully cleared all caches and stored credentials'); 16 | logger.newline(); 17 | }; 18 | 19 | // eslint-disable-next-line @typescript-eslint/no-floating-promises 20 | run(clear); 21 | 22 | // eslint-disable-next-line import/no-unused-modules 23 | export default clear; 24 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/src/scripts/info.ts: -------------------------------------------------------------------------------- 1 | import { logger } from '@twilio/flex-dev-utils'; 2 | 3 | import { packagesVersions } from '../prints'; 4 | import run from '../utils/run'; 5 | import { getPackageDetails, LIST_OF_PACKAGES } from '../utils/package'; 6 | 7 | /** 8 | * Prints the list of versions of important packages 9 | */ 10 | const info = async (): Promise => { 11 | logger.debug('Displaying information about the plugin'); 12 | 13 | const details = getPackageDetails(LIST_OF_PACKAGES); 14 | const found = details.filter((d) => d.found); 15 | const notFound = details.filter((d) => !d.found); 16 | 17 | packagesVersions(found, notFound); 18 | }; 19 | 20 | // eslint-disable-next-line @typescript-eslint/no-floating-promises 21 | run(info); 22 | 23 | // eslint-disable-next-line import/no-unused-modules 24 | export default info; 25 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/src/scripts/pre-localrun-check.ts: -------------------------------------------------------------------------------- 1 | import { logger } from '@twilio/flex-dev-utils'; 2 | import { checkRunPluginConfigurationExists } from '@twilio/flex-dev-utils/dist/fs'; 3 | 4 | import run from '../utils/run'; 5 | 6 | const preLocalRunCheck = async (...args: string[]): Promise => { 7 | logger.debug('Checking users environment is ready to run plugins locally'); 8 | 9 | // Slice the args to get rid of last two elements ('--core-cwd' and the 'cwd') 10 | await checkRunPluginConfigurationExists(args.slice(0, -2)); 11 | }; 12 | 13 | // eslint-disable-next-line @typescript-eslint/no-floating-promises 14 | run(preLocalRunCheck); 15 | 16 | // eslint-disable-next-line import/no-unused-modules 17 | export default preLocalRunCheck; 18 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/src/scripts/pre-start-check.ts: -------------------------------------------------------------------------------- 1 | import { existsSync } from 'fs'; 2 | 3 | import { logger, exit } from '@twilio/flex-dev-utils'; 4 | import { getPaths, addCWDNodeModule } from '@twilio/flex-dev-utils/dist/fs'; 5 | 6 | import { appConfigMissing } from '../prints'; 7 | import run from '../utils/run'; 8 | import { _setPluginDir } from './pre-script-check'; 9 | 10 | /** 11 | * Checks appConfig exists 12 | * 13 | * @private 14 | */ 15 | export const _checkAppConfig = (): void => { 16 | if (!existsSync(getPaths().app.appConfig)) { 17 | appConfigMissing(); 18 | 19 | exit(1); 20 | } 21 | }; 22 | 23 | /** 24 | * Runs pre-start/build checks 25 | */ 26 | const preScriptCheck = async (...args: string[]): Promise => { 27 | logger.debug('Checking Flex plugin project directory'); 28 | 29 | addCWDNodeModule(...args); 30 | 31 | _setPluginDir(...args); 32 | _checkAppConfig(); 33 | }; 34 | 35 | // eslint-disable-next-line @typescript-eslint/no-floating-promises 36 | run(preScriptCheck); 37 | 38 | // eslint-disable-next-line import/no-unused-modules 39 | export default preScriptCheck; 40 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/src/scripts/test/test.ts: -------------------------------------------------------------------------------- 1 | import { logger } from '@twilio/flex-dev-utils'; 2 | import { Environment } from '@twilio/flex-dev-utils/dist/env'; 3 | import { resolveModulePath } from '@twilio/flex-dev-utils/dist/fs'; 4 | import * as jest from 'jest'; 5 | 6 | import getConfiguration, { ConfigurationType } from '../../config'; 7 | 8 | /** 9 | * Runs jest 10 | * @param env 11 | * @param args 12 | */ 13 | /* c8 ignore next */ 14 | export default async (env: string, ...args: string[]): Promise => { 15 | const config = await getConfiguration(ConfigurationType.Jest, Environment.Test, false); 16 | const runArgs: string[] = [...args]; 17 | 18 | runArgs.push('--config', JSON.stringify(config)); 19 | const jestEnvPath = resolveModulePath(`jest-environment-${env}`); 20 | const envPath = resolveModulePath(env); 21 | if (jestEnvPath) { 22 | runArgs.push('--env', jestEnvPath); 23 | } else if (envPath) { 24 | runArgs.push('--env', envPath); 25 | } else { 26 | logger.warning(`jest-environment ${env} was not found`); 27 | } 28 | 29 | await jest.run(runArgs); 30 | }; 31 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/src/utils/package.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | 3 | import { resolveModulePath } from '@twilio/flex-dev-utils/dist/fs'; 4 | 5 | export const FLEX_PACKAGES: string[] = [ 6 | '@twilio/flex-ui', 7 | '@twilio/flex-plugin-scripts', 8 | '@twilio/flex-plugin', 9 | '@twilio/flex-dev-utils', 10 | ]; 11 | 12 | /* c8 ignore next */ 13 | export const LIST_OF_PACKAGES: string[] = [...FLEX_PACKAGES, 'react', 'react-dom', 'redux', 'react-redux']; 14 | 15 | export interface PackageDetail { 16 | name: string; 17 | found: boolean; 18 | package: { 19 | name?: string; 20 | version?: string; 21 | }; 22 | } 23 | 24 | /** 25 | * @param packages 26 | */ 27 | /* c8 ignore next */ 28 | export const getPackageDetails = (packages: string[]): PackageDetail[] => { 29 | return packages.map((name) => { 30 | const detail: PackageDetail = { 31 | name, 32 | found: false, 33 | package: {}, 34 | }; 35 | 36 | try { 37 | const resolvedPath = resolveModulePath(join(name, 'package.json')); 38 | if (resolvedPath) { 39 | // eslint-disable-next-line global-require, @typescript-eslint/no-require-imports 40 | detail.package = require(resolvedPath); 41 | detail.found = true; 42 | } 43 | } catch (e) { 44 | detail.found = false; 45 | } 46 | 47 | return detail; 48 | }); 49 | }; 50 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/src/utils/run.ts: -------------------------------------------------------------------------------- 1 | import { runner } from '@twilio/flex-dev-utils'; 2 | import { Callback } from '@twilio/flex-dev-utils/dist/runner'; 3 | 4 | /** 5 | * Runs the callback function if the process is spawned. 6 | * 7 | * @param callback 8 | */ 9 | export default async (callback: Callback): Promise => { 10 | if (process.argv.includes('--run-script')) { 11 | await runner(callback, ...process.argv.splice(2)); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "baseUrl": ".", 6 | "rootDir": "src", 7 | "esModuleInterop": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/flex-plugin-scripts/tsconfig.lint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": [ 4 | "node_modules", 5 | "dist" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /packages/flex-plugin-test/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (C) 2018, Twilio, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /packages/flex-plugin-test/README.md: -------------------------------------------------------------------------------- 1 | ![npm](https://img.shields.io/npm/v/flex-plugin-test.svg?style=square) 2 | ![npm](https://img.shields.io/npm/dt/flex-plugin-test.svg?style=square) 3 | [![NpmLicense](https://img.shields.io/npm/l/flex-plugin-test.svg?style=square)](../../LICENSE) 4 | 5 | # Flex Plugin Test 6 | 7 | To be filled 8 | 9 | ## Contributors 10 | 11 | Thank you to all the lovely contributors to this project. Please check the main repository to see [all contributors](https://github.com/twilio/flex-plugin-builder#contributors). 12 | 13 | ## License 14 | 15 | [MIT](../../LICENSE) 16 | -------------------------------------------------------------------------------- /packages/flex-plugin-test/jest-preset.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist').default(); 2 | -------------------------------------------------------------------------------- /packages/flex-plugin-test/jest.config.js: -------------------------------------------------------------------------------- 1 | const base = require('./../../jest.base.js'); 2 | const pkg = require('./package'); 3 | 4 | module.exports = { 5 | rootDir: '.', 6 | ...base(pkg), 7 | }; 8 | -------------------------------------------------------------------------------- /packages/flex-plugin-test/src/jestTransforms/babel.ts: -------------------------------------------------------------------------------- 1 | /* c8 ignore start */ 2 | import babelJest from 'babel-jest'; 3 | 4 | module.exports = (babelJest as any).createTransformer({ 5 | presets: [require.resolve('babel-preset-react-app')], 6 | babelrc: false, 7 | configFile: false, 8 | }); 9 | /* c8 ignore stop */ 10 | -------------------------------------------------------------------------------- /packages/flex-plugin-test/src/jestTransforms/css.ts: -------------------------------------------------------------------------------- 1 | /* c8 ignore start */ 2 | 3 | module.exports = { 4 | process() { 5 | return 'module.exports = {};'; 6 | }, 7 | getCacheKey() { 8 | // The output is always the same. 9 | return 'cssTransform'; 10 | }, 11 | }; 12 | /* c8 ignore stop */ 13 | -------------------------------------------------------------------------------- /packages/flex-plugin-test/templates/setupTests.js: -------------------------------------------------------------------------------- 1 | require('babel-polyfill'); 2 | 3 | const configure = require('enzyme/build').configure; 4 | const Adapter = require('enzyme-adapter-react-16/build'); 5 | 6 | configure({ 7 | adapter: new Adapter(), 8 | }); 9 | -------------------------------------------------------------------------------- /packages/flex-plugin-test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "baseUrl": ".", 6 | "rootDir": "src", 7 | "esModuleInterop": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/flex-plugin-test/tsconfig.lint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": [ 4 | "node_modules", 5 | "dist" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /packages/flex-plugin-utils-jest/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (C) 2018, Twilio, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /packages/flex-plugin-utils-jest/README.md: -------------------------------------------------------------------------------- 1 | ![npm](https://img.shields.io/npm/v/flex-plugin-utils-jest.svg?style=square) 2 | ![npm](https://img.shields.io/npm/dt/flex-plugin-utils-jest.svg?style=square) 3 | [![NpmLicense](https://img.shields.io/npm/l/flex-plugin-utils-jest.svg?style=square)](../../LICENSE) 4 | 5 | # Flex Plugin Utils Jest 6 | 7 | Jest extensions used throughout Flex Plugin Builder 8 | 9 | ## Extension 10 | 11 | This library provides the following extensions to Jest 12 | 13 | ### toMatchPath(expected: string) 14 | 15 | This method can be used to check for path and is OS agnostic. 16 | 17 | ```bash 18 | // Use symmetrically 19 | expect('/path/to/file1').toMatchPath('/path/to/file1'); 20 | 21 | // Use asymmetrically 22 | expect(fn).toHaveBeenCalledWith(expect.toMatchPath('/path/to/file1')); 23 | ``` 24 | 25 | ### toMatchPathContaining(expected: string) 26 | 27 | Similar to `toMatchPath` but will do partial match 28 | 29 | ```bash 30 | // Use symmetrically 31 | expect('/path/to/file1').toMatchPathContaining('to/file1'); 32 | 33 | // Use asymmetrically 34 | expect(fn).toHaveBeenCalledWith(expect.toMatchPathContaining('to/file1')); 35 | ``` 36 | 37 | ## Contributors 38 | 39 | Thank you to all the lovely contributors to this project. Please check the main repository to see [all contributors](https://github.com/twilio/flex-plugin-builder#contributors). 40 | 41 | ## License 42 | 43 | [MIT](../../LICENSE) 44 | -------------------------------------------------------------------------------- /packages/flex-plugin-utils-jest/jest.config.js: -------------------------------------------------------------------------------- 1 | const base = require('./../../jest.base.js'); 2 | const pkg = require('./package'); 3 | 4 | module.exports = { 5 | rootDir: '.', 6 | ...base(pkg), 7 | coverageThreshold: { 8 | global: { 9 | statements: 94, 10 | branches: 83, 11 | lines: 94, 12 | functions: 78, 13 | }, 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /packages/flex-plugin-utils-jest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@twilio/flex-plugin-utils-jest", 3 | "version": "7.1.1", 4 | "description": "Flex Plugin E2E Tests framework", 5 | "keywords": [ 6 | "flex", 7 | "plugins", 8 | "scripts", 9 | "twilio", 10 | "jest" 11 | ], 12 | "homepage": "https://github.com/twilio/flex-plugin-builder", 13 | "bugs": { 14 | "url": "https://github.com/twilio/flex-plugin-builder/issues" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/twilio/flex-plugin-builder.git" 19 | }, 20 | "license": "MIT", 21 | "author": "Flex Runtime ", 22 | "main": "dist/index.js", 23 | "types": "dist/index.d.ts", 24 | "files": [ 25 | "dist" 26 | ], 27 | "scripts": { 28 | "prebuild": "npm run clean", 29 | "build": "cd ../.. && tsc -p \"packages/flex-plugin-utils-jest/tsconfig.json\"", 30 | "clean": "rm -rf dist", 31 | "lint": "eslint --ext ts src/", 32 | "lint:fix": "npm run lint -- --fix", 33 | "test": "exit 0" 34 | }, 35 | "devDependencies": { 36 | "jest-matcher-utils": "^26.6.2" 37 | }, 38 | "engines": { 39 | "node": "^16 || ^18 || ^20 || ^22" 40 | }, 41 | "publishConfig": { 42 | "access": "public" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/flex-plugin-utils-jest/src/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-namespace, @typescript-eslint/no-unused-vars */ 2 | import * as matchers from './matchers'; 3 | import * as scripts from './utils'; 4 | 5 | declare const utils: typeof scripts; 6 | 7 | declare global { 8 | namespace NodeJS { 9 | interface Global { 10 | utils: typeof scripts; 11 | } 12 | } 13 | 14 | namespace jest { 15 | interface Matchers { 16 | toMatchPath: InstanceType['match']; 17 | toMatchPathContaining: InstanceType['match']; 18 | } 19 | 20 | interface Expect { 21 | toMatchPath: typeof matchers.toMatchPath; 22 | toMatchPathContaining: typeof matchers.toMatchPathContaining; 23 | } 24 | } 25 | } 26 | 27 | if (expect) { 28 | const extensions = Object.keys(matchers) 29 | .filter((k) => k.charAt(0).toLocaleLowerCase() === k.charAt(0)) 30 | .reduce((extension, key) => { 31 | // eslint-disable-next-line import/namespace, @typescript-eslint/no-explicit-any 32 | extension[key] = (actual: any, ...expected: any[]) => matchers[key](actual).match(...expected); 33 | return extension; 34 | }, {}); 35 | expect.extend(extensions); 36 | 37 | // @ts-ignore 38 | global.utils = scripts; 39 | } else { 40 | // eslint-disable-next-line no-console 41 | console.error("Unable to find Jest's global expect"); 42 | } 43 | -------------------------------------------------------------------------------- /packages/flex-plugin-utils-jest/src/matchers/AsymmetricMatcher.ts: -------------------------------------------------------------------------------- 1 | import { matcherHint, printExpected, printReceived } from 'jest-matcher-utils'; 2 | 3 | /** 4 | * Abstract class for writing asymmetric matchers 5 | */ 6 | /* c8 ignore next */ 7 | export abstract class AsymmetricMatcher implements jest.AsymmetricMatcher { 8 | $$typeof: symbol; 9 | 10 | inverse?: boolean; 11 | 12 | protected actual: T; 13 | 14 | protected constructor(actual: T) { 15 | this.$$typeof = Symbol.for('jest.asymmetricMatcher'); 16 | this.actual = actual; 17 | } 18 | 19 | toAsymmetricMatcher(): string { 20 | return `${this.toString()}<${this.actual}>`; 21 | } 22 | 23 | protected passMessage = (actual: string, expected: string) => (): string => 24 | `${matcherHint(`.not.${this.method()}`)} 25 | 26 | Expected value not to match: 27 | ${printExpected(expected)} 28 | Received: 29 | ${printReceived(actual)}`; 30 | 31 | protected failMessage = (actual: string, expected: string) => (): string => 32 | `${matcherHint(`.${this.method()}`)} 33 | 34 | Expected value to match: 35 | ${printExpected(expected)} 36 | Received: 37 | ${printReceived(actual)}`; 38 | 39 | abstract method(): string; 40 | 41 | abstract asymmetricMatch(other: T): boolean; 42 | 43 | abstract match(other?: T): jest.CustomMatcherResult; 44 | } 45 | -------------------------------------------------------------------------------- /packages/flex-plugin-utils-jest/src/matchers/index.ts: -------------------------------------------------------------------------------- 1 | export { default as toMatchPath, ToMatchPath } from './toMatchPath'; 2 | export { default as toMatchPathContaining, ToMatchPathContaining } from './toMatchPathContaining'; 3 | -------------------------------------------------------------------------------- /packages/flex-plugin-utils-jest/src/matchers/toMatchPath.ts: -------------------------------------------------------------------------------- 1 | import { AsymmetricMatcher } from './AsymmetricMatcher'; 2 | import * as utils from '../utils'; 3 | 4 | export class ToMatchPath extends AsymmetricMatcher { 5 | constructor(actual: string, inverse: boolean = false) { 6 | super(actual); 7 | 8 | this.inverse = inverse; 9 | } 10 | 11 | asymmetricMatch(expected: string): boolean { 12 | return this.actual === utils.normalizePath(expected); 13 | } 14 | 15 | match(expected: string): jest.CustomMatcherResult { 16 | const pass = this.asymmetricMatch(expected); 17 | return { 18 | pass, 19 | message: pass ? this.passMessage(this.actual, expected) : this.failMessage(this.actual, expected), 20 | }; 21 | } 22 | 23 | method(): string { 24 | return 'toMatchPath'; 25 | } 26 | 27 | toString(): string { 28 | return 'ToMatchPath'; 29 | } 30 | } 31 | 32 | export default (actual: string): ToMatchPath => new ToMatchPath(actual); 33 | -------------------------------------------------------------------------------- /packages/flex-plugin-utils-jest/src/matchers/toMatchPathContaining.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | import { AsymmetricMatcher } from './AsymmetricMatcher'; 4 | 5 | export class ToMatchPathContaining extends AsymmetricMatcher { 6 | constructor(actual: string, inverse: boolean = false) { 7 | super(actual); 8 | 9 | this.inverse = inverse; 10 | } 11 | 12 | asymmetricMatch(expected: string): boolean { 13 | return this.actual.includes(path.normalize(expected)); 14 | } 15 | 16 | match(expected: string): jest.CustomMatcherResult { 17 | const pass = this.asymmetricMatch(expected); 18 | return { 19 | pass, 20 | message: pass ? this.passMessage(this.actual, expected) : this.failMessage(this.actual, expected), 21 | }; 22 | } 23 | 24 | method(): string { 25 | return 'toMatchPathContaining'; 26 | } 27 | 28 | toString(): string { 29 | return 'ToMatchPathContaining'; 30 | } 31 | } 32 | 33 | export default (actual: string): ToMatchPathContaining => new ToMatchPathContaining(actual); 34 | -------------------------------------------------------------------------------- /packages/flex-plugin-utils-jest/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import os from 'os'; 3 | 4 | /** 5 | * Returns if platform is windows 6 | */ 7 | export const isWin = (): boolean => os.platform() === 'win32'; 8 | 9 | /** 10 | * Normalizes the path 11 | * @param parts 12 | */ 13 | export const normalizePath = (...parts: string[]): string => { 14 | let normalized = path.normalize(path.join(...parts)); 15 | if (isWin() && parts[0].charAt(0) === '/') { 16 | normalized = `C:${normalized}`; 17 | } 18 | 19 | return normalized; 20 | }; 21 | -------------------------------------------------------------------------------- /packages/flex-plugin-utils-jest/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "baseUrl": ".", 6 | "rootDir": "src", 7 | "esModuleInterop": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/flex-plugin-utils-jest/tsconfig.lint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": [ 4 | "node_modules", 5 | "dist" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /packages/flex-plugin-webpack/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (C) 2018, Twilio, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /packages/flex-plugin-webpack/README.md: -------------------------------------------------------------------------------- 1 | ![npm](https://img.shields.io/npm/v/flex-plugin-webpack.svg?style=square) 2 | ![npm](https://img.shields.io/npm/dt/flex-plugin-webpack.svg?style=square) 3 | [![NpmLicense](https://img.shields.io/npm/l/flex-plugin-webpack.svg?style=square)](../../LICENSE) 4 | 5 | # Flex Plugin Webpack 6 | 7 | To be filled 8 | 9 | ## Contributors 10 | 11 | Thank you to all the lovely contributors to this project. Please check the main repository to see [all contributors](https://github.com/twilio/flex-plugin-builder#contributors). 12 | 13 | ## License 14 | 15 | [MIT](../../LICENSE) 16 | -------------------------------------------------------------------------------- /packages/flex-plugin-webpack/jest.config.js: -------------------------------------------------------------------------------- 1 | const base = require('./../../jest.base.js'); 2 | const pkg = require('./package'); 3 | 4 | module.exports = { 5 | rootDir: '.', 6 | ...base(pkg), 7 | coverageThreshold: { 8 | global: { 9 | statements: 61, 10 | branches: 35, 11 | lines: 58, 12 | functions: 49, 13 | }, 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /packages/flex-plugin-webpack/src/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-unused-modules */ 2 | /* c8 ignore start */ 3 | 4 | import webpack from 'webpack'; 5 | 6 | export enum WebpackType { 7 | Static = 'static', 8 | JavaScript = 'javascript', 9 | Complete = 'complete', 10 | } 11 | 12 | export { webpack }; 13 | export { Configuration as WebpackConfigurations, Compiler as WebpackCompiler } from 'webpack'; 14 | export { Configuration as WebpackDevConfigurations } from 'webpack-dev-server'; 15 | 16 | export { default as webpackFactory } from './webpack/webpack.config'; 17 | export { default as webpackDevFactory } from './webpack/webpack.dev'; 18 | export { default as compiler, compilerRenderer } from './compiler'; 19 | export { default as webpackDevServer } from './devServer/webpackDevServer'; 20 | export { default as pluginServer, Plugin, PluginsConfig, PLUGIN_INPUT_PARSER_REGEX } from './devServer/pluginServer'; 21 | export { 22 | emitCompileComplete, 23 | emitDevServerCrashed, 24 | emitAllCompilesComplete, 25 | IPCType, 26 | onIPCServerMessage, 27 | OnDevServerCrashedPayload, 28 | startIPCClient, 29 | startIPCServer, 30 | } from './devServer/ipcServer'; 31 | export { default as DelayRenderStaticPlugin } from './plugins/DelayRenderStaticPlugin'; 32 | 33 | /* c8 ignore stop */ 34 | -------------------------------------------------------------------------------- /packages/flex-plugin-webpack/src/module.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'pnp-webpack-plugin'; 2 | -------------------------------------------------------------------------------- /packages/flex-plugin-webpack/src/plugins/DelayRenderStaticPlugin.ts: -------------------------------------------------------------------------------- 1 | /* c8 ignore start */ 2 | 3 | import { Compiler } from '../compiler'; 4 | import { onIPCServerMessage, IPCType } from '../devServer/ipcServer'; 5 | 6 | let isCalled = false; 7 | 8 | export default class DelayRenderStaticPlugin { 9 | apply(compiler: Compiler): void { 10 | compiler.hooks.beforeCompile.tapAsync('DelayPlugin', async (_, done) => { 11 | if (isCalled) { 12 | done(); 13 | } 14 | onIPCServerMessage(IPCType.onEmitAllCompilesComplete, () => { 15 | isCalled = true; 16 | done(); 17 | }); 18 | }); 19 | } 20 | } 21 | 22 | /* c8 ignore stop */ 23 | -------------------------------------------------------------------------------- /packages/flex-plugin-webpack/src/prints/dotEnvIncorrectVariable.ts: -------------------------------------------------------------------------------- 1 | import { logger, env } from '@twilio/flex-dev-utils'; 2 | 3 | export default (filename: string, key: string): void => { 4 | env.persistTerminal(); 5 | 6 | const isQuiet = env.isQuiet(); 7 | env.setQuiet(false); 8 | logger.warning( 9 | `Unsupported variable **${key}** provided in **${filename}** file. Variables must start with either FLEX_APP_ or REACT_APP_.`, 10 | ); 11 | env.setQuiet(isQuiet); 12 | }; 13 | -------------------------------------------------------------------------------- /packages/flex-plugin-webpack/src/prints/index.ts: -------------------------------------------------------------------------------- 1 | export { default as devServerSuccessful } from './devServerSuccessful'; 2 | export { default as remotePluginNotFound } from './remotePluginNotFound'; 3 | export { default as dotEnvIncorrectVariable } from './dotEnvIncorrectVariable'; 4 | -------------------------------------------------------------------------------- /packages/flex-plugin-webpack/src/prints/remotePluginNotFound.ts: -------------------------------------------------------------------------------- 1 | import { logger } from '@twilio/flex-dev-utils'; 2 | 3 | import { Plugin } from '..'; 4 | 5 | /** 6 | * Error message for when a remote plugin is not found 7 | * 8 | * @param notFoundPlugins name of the plugin 9 | * @param remotePlugins array of remote plugins 10 | */ 11 | export default (notFoundPlugins: string[], remotePlugins: Plugin[]): void => { 12 | logger.clearTerminal(); 13 | 14 | logger.error('Server not loading because these plugins were not found remotely:'); 15 | logger.newline(); 16 | for (const plugin of notFoundPlugins) { 17 | logger.error('\t', logger.colors.bold(plugin)); 18 | } 19 | 20 | logger.newline(); 21 | logger.error('Your remote plugins are:'); 22 | logger.newline(); 23 | for (const plugin of remotePlugins) { 24 | logger.info(`\t--**${plugin.name}**..@..**${plugin.version}**--`); 25 | } 26 | 27 | logger.newline(); 28 | }; 29 | -------------------------------------------------------------------------------- /packages/flex-plugin-webpack/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "baseUrl": ".", 6 | "rootDir": "src", 7 | "esModuleInterop": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/flex-plugin-webpack/tsconfig.lint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": [ 4 | "node_modules", 5 | "dist" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /packages/flex-plugin/jest.config.js: -------------------------------------------------------------------------------- 1 | const base = require('./../../jest.base.js'); 2 | const pkg = require('./package'); 3 | 4 | module.exports = { 5 | rootDir: '.', 6 | ...base(pkg), 7 | coverageThreshold: { 8 | global: { 9 | statements: 97, 10 | branches: 81, 11 | lines: 97, 12 | functions: 100, 13 | }, 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /packages/flex-plugin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@twilio/flex-plugin", 3 | "version": "7.1.1", 4 | "description": "Runtime for Flex plugins", 5 | "keywords": [ 6 | "builder", 7 | "flex", 8 | "plugin", 9 | "twilio" 10 | ], 11 | "homepage": "https://github.com/twilio/flex-plugin-builder", 12 | "bugs": { 13 | "url": "https://github.com/twilio/flex-plugin-builder/issues" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/twilio/flex-plugin-builder.git" 18 | }, 19 | "license": "MIT", 20 | "author": "Flex Runtime ", 21 | "main": "dist/index.js", 22 | "types": "dist/index.d.ts", 23 | "directories": { 24 | "lib": "src", 25 | "test": "__tests__" 26 | }, 27 | "files": [ 28 | "dist" 29 | ], 30 | "scripts": { 31 | "prebuild": "npm run clean", 32 | "build": "cd ../.. && tsc -p \"packages/flex-plugin/tsconfig.json\"", 33 | "clean": "rm -rf dist", 34 | "lint": "eslint --ext ts src/", 35 | "lint:fix": "npm run lint -- --fix", 36 | "test": "cd ../.. && jest packages/flex-plugin --color", 37 | "test:watch": "cd ../.. && jest packages/flex-plugin --watch --color" 38 | }, 39 | "devDependencies": { 40 | "@twilio/flex-ui": "^1" 41 | }, 42 | "publishConfig": { 43 | "access": "public" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/flex-plugin/src/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-unused-modules */ 2 | export { loadPlugin, FlexPlugin } from './lib/flex-plugin'; 3 | 4 | export { getAssetsUrl, getRuntimeUrl } from './utils/runtime'; 5 | 6 | export { loadJS } from './utils/loadJS'; 7 | export { loadCSS } from './utils/loadCSS'; 8 | -------------------------------------------------------------------------------- /packages/flex-plugin/src/module.d.ts: -------------------------------------------------------------------------------- 1 | interface Window { 2 | appConfig?: Record; 3 | } 4 | 5 | declare const Twilio: { 6 | Flex: { 7 | Plugins: { 8 | init(FlexPlugin); 9 | }; 10 | }; 11 | }; 12 | 13 | declare const __FPB_PLUGIN_UNIQUE_NAME: string; 14 | declare const __FPB_PLUGIN_VERSION: string; 15 | declare const __FPB_FLEX_PLUGIN_SCRIPTS_VERSION: string; 16 | declare const __FPB_FLEX_PLUGIN_VERSION: string; 17 | declare const __FPB_FLEX_UI_VERSION: string; 18 | declare const __FPB_REACT_VERSION: string; 19 | declare const __FPB_REACT_DOM_VERSION: string; 20 | -------------------------------------------------------------------------------- /packages/flex-plugin/src/utils/__tests__/loadCSS.test.ts: -------------------------------------------------------------------------------- 1 | import { loadCSS } from '../loadCSS'; 2 | 3 | describe('loadCSS', () => { 4 | const CSS_LINK = 'https://my-publicly-accessible-domain.com/test.css'; 5 | const CSS_LINK_2 = 'https://my-publicly-accessible-domain.com/test-2.css'; 6 | 7 | afterEach(() => { 8 | document.getElementsByTagName('html')[0].innerHTML = ''; 9 | }); 10 | 11 | it('should load the CSS file into the head', () => { 12 | expect(document.head.getElementsByTagName('link').length).toBe(0); 13 | 14 | loadCSS(CSS_LINK); 15 | 16 | const links = document.head.getElementsByTagName('link'); 17 | expect(links.length).toBe(1); 18 | expect(links[0].href).toBe(CSS_LINK); 19 | 20 | expect(document.body.getElementsByTagName('link').length).toBe(0); 21 | }); 22 | 23 | it('should load the multiple CSS files into the head', () => { 24 | expect(document.head.getElementsByTagName('link').length).toBe(0); 25 | 26 | loadCSS(CSS_LINK, CSS_LINK_2); 27 | 28 | const links = document.head.getElementsByTagName('link'); 29 | expect(links.length).toBe(2); 30 | expect(links[0].href).toBe(CSS_LINK); 31 | expect(links[1].href).toBe(CSS_LINK_2); 32 | 33 | expect(document.body.getElementsByTagName('link').length).toBe(0); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /packages/flex-plugin/src/utils/__tests__/loadJS.test.ts: -------------------------------------------------------------------------------- 1 | import { loadJS } from '../loadJS'; 2 | 3 | describe('loadJS', () => { 4 | const JS_LINK = 'https://my-publicly-accessible-domain.com/test.js'; 5 | const JS_LINK_2 = 'https://my-publicly-accessible-domain.com/test-2.js'; 6 | 7 | afterEach(() => { 8 | document.getElementsByTagName('html')[0].innerHTML = ''; 9 | }); 10 | 11 | it('should load the JS file into the body', () => { 12 | expect(document.body.getElementsByTagName('script').length).toBe(0); 13 | 14 | loadJS(JS_LINK); 15 | 16 | const scripts = document.body.getElementsByTagName('script'); 17 | expect(scripts.length).toBe(1); 18 | expect(scripts[0].src).toBe(JS_LINK); 19 | 20 | expect(document.head.getElementsByTagName('script').length).toBe(0); 21 | }); 22 | 23 | it('should load the multiple CSS files into the head', () => { 24 | expect(document.body.getElementsByTagName('script').length).toBe(0); 25 | 26 | loadJS(JS_LINK, JS_LINK_2); 27 | 28 | const scripts = document.body.getElementsByTagName('script'); 29 | expect(scripts.length).toBe(2); 30 | expect(scripts[0].src).toBe(JS_LINK); 31 | expect(scripts[1].src).toBe(JS_LINK_2); 32 | 33 | expect(document.head.getElementsByTagName('script').length).toBe(0); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /packages/flex-plugin/src/utils/__tests__/runtime.test.ts: -------------------------------------------------------------------------------- 1 | import { getAssetsUrl, getRuntimeUrl } from '../runtime'; 2 | 3 | describe('runtime', () => { 4 | describe('getRuntimeUrl', () => { 5 | const currentScript = { 6 | src: 'https://foo.twil.io/', 7 | }; 8 | 9 | beforeAll(() => { 10 | // @ts-ignore 11 | Object.defineProperty(global.document, 'currentScript', { value: currentScript }); 12 | }); 13 | 14 | it('should return runtime url', () => { 15 | const url = getRuntimeUrl(); 16 | 17 | expect(url).toEqual('https://foo.twil.io'); 18 | }); 19 | }); 20 | 21 | describe('getAssetsUrl', () => { 22 | it('should return a string', () => { 23 | const assetsUrl: string = getAssetsUrl(); 24 | expect(typeof assetsUrl).toEqual('string'); 25 | }); 26 | 27 | it('should postfix an `/assets`', () => { 28 | // Act 29 | const assetsUrl: string = getAssetsUrl(); 30 | 31 | // Assert 32 | expect(assetsUrl).toMatch(/\/assets$/); 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /packages/flex-plugin/src/utils/loadCSS.ts: -------------------------------------------------------------------------------- 1 | import shortid from './shortid'; 2 | 3 | /** 4 | * Loads external CSS files into your plugin 5 | * Use this method at the beginning of the init() method of the plugin 6 | * @param hrefArray Array of CSS file links to load 7 | * @return {void} 8 | */ 9 | export const loadCSS = (...hrefArray: string[]): void => { 10 | hrefArray.forEach((href: string) => { 11 | const link = document.createElement('link'); 12 | 13 | link.id = `external-css-${shortid()}`; 14 | link.rel = 'stylesheet'; 15 | link.type = 'text/css'; 16 | link.media = 'all'; 17 | link.href = href; 18 | 19 | document.head.appendChild(link); 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /packages/flex-plugin/src/utils/loadJS.ts: -------------------------------------------------------------------------------- 1 | import shortid from './shortid'; 2 | 3 | /** 4 | * Loads external JS files into your plugin. 5 | * Use this method at the beginning of the init() method of the plugin. 6 | * @param srcArray Array of JS file links to load 7 | * @return {void} 8 | */ 9 | export const loadJS = (...srcArray: string[]): void => { 10 | srcArray.forEach((src: string) => { 11 | const script = document.createElement('script'); 12 | 13 | script.id = `external-js-${shortid()}`; 14 | script.type = 'text/javascript'; 15 | script.src = src; 16 | 17 | document.body.appendChild(script); 18 | }); 19 | }; 20 | -------------------------------------------------------------------------------- /packages/flex-plugin/src/utils/runtime.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Gets the Twilio Runtime URL 3 | * @return {string} the url of Twilio Runtime 4 | */ 5 | export const getRuntimeUrl = (): string => { 6 | if (document && document.currentScript) { 7 | const pluginScript = document.currentScript as HTMLScriptElement; 8 | 9 | if (typeof pluginScript.src === 'string') { 10 | const pluginUrl = (pluginScript as HTMLScriptElement).src; 11 | return pluginUrl.substr(0, pluginUrl.lastIndexOf('/')); 12 | } 13 | } 14 | 15 | return ''; 16 | }; 17 | 18 | /** 19 | * Gets the base URL for Twilio Runtime Assets 20 | * @return {string} the url of Assets 21 | */ 22 | export const getAssetsUrl = (): string => { 23 | return `${getRuntimeUrl()}/assets`; 24 | }; 25 | -------------------------------------------------------------------------------- /packages/flex-plugin/src/utils/shortid.ts: -------------------------------------------------------------------------------- 1 | export default (): string => Math.random().toString(26).slice(2); 2 | -------------------------------------------------------------------------------- /packages/flex-plugin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "baseUrl": ".", 6 | "rootDir": "src" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/flex-plugin/tsconfig.lint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": [ 4 | "node_modules", 5 | "dist" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /packages/flex-plugins-api-client/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc", 3 | "rules": { 4 | "camelcase": "off", 5 | "import/no-unused-modules": "off" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/flex-plugins-api-client/jest.config.js: -------------------------------------------------------------------------------- 1 | const base = require('./../../jest.base.js'); 2 | const pkg = require('./package'); 3 | 4 | module.exports = { 5 | rootDir: '.', 6 | ...base(pkg), 7 | coverageThreshold: { 8 | global: { 9 | statements: 98, 10 | branches: 94, 11 | lines: 98, 12 | functions: 98, 13 | }, 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /packages/flex-plugins-api-client/src/clients/__tests__/configuredPluginsClient.test.ts: -------------------------------------------------------------------------------- 1 | import ConfiguredPluginsClient from '../configuredPlugins'; 2 | import PluginServiceHttpClient from '../client'; 3 | 4 | describe('ConfiguredPluginsClient', () => { 5 | const httpClient = new PluginServiceHttpClient('username', 'password'); 6 | const get = jest.spyOn(httpClient, 'get'); 7 | const list = jest.spyOn(httpClient, 'list'); 8 | const client = new ConfiguredPluginsClient(httpClient); 9 | 10 | beforeEach(() => { 11 | jest.resetAllMocks(); 12 | }); 13 | 14 | it('should list configured plugins', async () => { 15 | // @ts-ignore 16 | list.mockResolvedValue({ meta: {}, data: {} }); 17 | 18 | const result = await client.list('configId'); 19 | 20 | expect(result).toEqual({ meta: {}, data: {} }); 21 | expect(list).toHaveBeenCalledTimes(1); 22 | expect(list).toHaveBeenCalledWith('Configurations/configId/Plugins', 'plugins'); 23 | }); 24 | 25 | it('should get configured plugin', async () => { 26 | get.mockResolvedValue('item'); 27 | 28 | const result = await client.get('configId', 'pluginId'); 29 | 30 | expect(result).toEqual('item'); 31 | expect(get).toHaveBeenCalledTimes(1); 32 | expect(get).toHaveBeenCalledWith('Configurations/configId/Plugins/pluginId'); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /packages/flex-plugins-api-client/src/clients/client.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient, OptionalHttpClientConfig } from '@twilio/flex-dev-utils/dist/http'; 2 | 3 | export type PluginServiceHttpOption = OptionalHttpClientConfig; 4 | 5 | /** 6 | * An implementation of the raw {@link HttpClient} but made for PluginService 7 | */ 8 | export default class PluginServiceHttp extends HttpClient { 9 | private static version = 'v1'; 10 | 11 | constructor(username: string, password: string, options?: PluginServiceHttpOption) { 12 | // eslint-disable-next-line global-require, @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports 13 | const pkg = require('../../package.json'); 14 | const caller = (options && options.caller) || pkg.name; 15 | const packages = (options && options.packages) || {}; 16 | packages[pkg.name] = pkg.version; 17 | 18 | super({ 19 | ...options, 20 | baseURL: `https://flex-api.twilio.com/${PluginServiceHttp.version}/PluginService`, 21 | auth: { username, password }, 22 | caller, 23 | packages, 24 | supportProxy: true, 25 | skipCacheSetup: true, 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/flex-plugins-api-client/src/clients/index.ts: -------------------------------------------------------------------------------- 1 | export { PaginationMeta, Meta, Pagination } from '@twilio/flex-dev-utils/dist/http'; 2 | export { default as PluginServiceHTTPClient } from './client'; 3 | export { 4 | default as PluginsClient, 5 | PluginResource, 6 | PluginResourcePage, 7 | UpdatePluginResource, 8 | CreatePluginResource, 9 | } from './plugins'; 10 | export { 11 | default as PluginVersionsClient, 12 | PluginVersionResource, 13 | PluginVersionResourcePage, 14 | CreatePluginVersionResource, 15 | ValidateStatus, 16 | } from './pluginVersions'; 17 | export { 18 | default as ConfigurationsClient, 19 | ConfigurationResource, 20 | ConfigurationResourcePage, 21 | CreateConfiguredPlugin, 22 | CreateConfigurationResource, 23 | } from './configurations'; 24 | export { 25 | default as ConfiguredPluginsClient, 26 | ConfiguredPluginResource, 27 | ConfiguredPluginResourcePage, 28 | } from './configuredPlugins'; 29 | export { default as ReleasesClient, ReleaseResource, ReleaseResourcePage, CreateReleaseResource } from './releases'; 30 | -------------------------------------------------------------------------------- /packages/flex-plugins-api-client/src/toolkit/__tests__/flexPluginsAPIToolkitBase.test.ts: -------------------------------------------------------------------------------- 1 | import { PluginServiceHTTPClient } from '../../clients'; 2 | import FlexPluginsAPIToolkitBase from '../flexPluginsAPIToolkitBase'; 3 | 4 | describe('FlexPluginsAPIToolkitBase', () => { 5 | it('should load toolkit', () => { 6 | const mockHttpClient = jest.fn(); 7 | const toolkit = new FlexPluginsAPIToolkitBase(mockHttpClient as unknown as PluginServiceHTTPClient); 8 | expect(toolkit.describePlugin).toBeTruthy(); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /packages/flex-plugins-api-client/src/toolkit/scripts/__tests__/release.test.ts: -------------------------------------------------------------------------------- 1 | import { PluginServiceHTTPClient, ReleaseResource, ReleasesClient } from '../../../clients'; 2 | import releaseScript from '../release'; 3 | 4 | describe('ReleaseScript', () => { 5 | const httpClient = new PluginServiceHTTPClient('username', 'password'); 6 | const releaseClient = new ReleasesClient(httpClient); 7 | 8 | const create = jest.spyOn(releaseClient, 'create'); 9 | 10 | const release: ReleaseResource = { 11 | sid: 'FK00000000000000000000000000000000', 12 | account_sid: 'AC00000000000000000000000000000000', 13 | configuration_sid: 'FJ00000000000000000000000000000000', 14 | date_created: '', 15 | }; 16 | 17 | const script = releaseScript(releaseClient); 18 | 19 | beforeEach(() => { 20 | jest.resetAllMocks(); 21 | }); 22 | 23 | it('should create a release', async () => { 24 | create.mockResolvedValue(release); 25 | const result = await script({ configurationSid: release.configuration_sid }); 26 | 27 | expect(create).toHaveBeenCalledTimes(1); 28 | expect(create).toHaveBeenCalledWith({ ConfigurationId: release.configuration_sid }); 29 | expect(result).toEqual({ 30 | configurationSid: release.configuration_sid, 31 | releaseSid: release.sid, 32 | dateCreated: release.date_created, 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /packages/flex-plugins-api-client/src/toolkit/scripts/archiveConfiguration.ts: -------------------------------------------------------------------------------- 1 | import { ConfigurationsClient } from '../../clients'; 2 | import { Script, Configuration } from '.'; 3 | 4 | export interface ArchiveConfigurationOption { 5 | sid: string; 6 | } 7 | 8 | export type ArchiveConfigurationScript = Script; 9 | 10 | /** 11 | * The .archiveConfiguration script. This script archives a configuration 12 | * @param configurationsClient the Public API {@link ConfigurationsClient} 13 | */ 14 | export default function archiveConfiguration(configurationsClient: ConfigurationsClient): ArchiveConfigurationScript { 15 | return async (options: ArchiveConfigurationOption) => { 16 | const configuration = await configurationsClient.archive(options.sid); 17 | 18 | return { 19 | sid: configuration.sid, 20 | name: configuration.name, 21 | description: configuration.description, 22 | isArchived: configuration.archived, 23 | dateCreated: configuration.date_created, 24 | }; 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /packages/flex-plugins-api-client/src/toolkit/scripts/archivePlugin.ts: -------------------------------------------------------------------------------- 1 | import { PluginsClient } from '../../clients'; 2 | import { Script, Plugin } from '.'; 3 | 4 | export interface ArchivePluginOption { 5 | name: string; 6 | } 7 | 8 | export type ArchivePluginScript = Script; 9 | 10 | /** 11 | * The .archivePlugin script. This script archives a plugin 12 | * @param pluginsClient the Public API {@link PluginsClient} 13 | */ 14 | export default function archivePlugin(pluginsClient: PluginsClient): ArchivePluginScript { 15 | return async (options: ArchivePluginOption) => { 16 | const plugin = await pluginsClient.archive(options.name); 17 | 18 | return { 19 | sid: plugin.sid, 20 | name: plugin.unique_name, 21 | friendlyName: plugin.friendly_name, 22 | description: plugin.description, 23 | isArchived: plugin.archived, 24 | dateCreated: plugin.date_created, 25 | dateUpdated: plugin.date_updated, 26 | }; 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /packages/flex-plugins-api-client/src/toolkit/scripts/archivePluginVersion.ts: -------------------------------------------------------------------------------- 1 | import { PluginVersionsClient } from '../../clients'; 2 | import { Script, PluginVersion } from '.'; 3 | 4 | export interface ArchivePluginVersionOption { 5 | name: string; 6 | version: string; 7 | } 8 | 9 | export type ArchivePluginVersionScript = Script; 10 | 11 | /** 12 | * The .archivePluginVersion script. This script archives a plugin version 13 | * @param pluginVersionsClient the Public API {@link PluginVersionsClient} 14 | */ 15 | export default function archivePluginVersion(pluginVersionsClient: PluginVersionsClient): ArchivePluginVersionScript { 16 | return async (options: ArchivePluginVersionOption) => { 17 | const pluginVersion = await pluginVersionsClient.archive(options.name, options.version); 18 | 19 | return { 20 | sid: pluginVersion.sid, 21 | version: pluginVersion.version, 22 | url: pluginVersion.plugin_url, 23 | changelog: pluginVersion.changelog, 24 | isPrivate: pluginVersion.private, 25 | isArchived: pluginVersion.archived, 26 | dateCreated: pluginVersion.date_created, 27 | isValidated: pluginVersion.validated, 28 | }; 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /packages/flex-plugins-api-client/src/toolkit/scripts/listReleases.ts: -------------------------------------------------------------------------------- 1 | import { ReleasesClient } from '../../clients'; 2 | import { ListResource, Page, ResourceNames, Script } from '.'; 3 | 4 | export type ListReleasesOption = Page; 5 | 6 | export interface ListReleases { 7 | sid: string; 8 | configurationSid: string; 9 | dateCreated: string; 10 | } 11 | 12 | export type ListReleasesResource = ListResource; 13 | export type ListReleasesScript = Script; 14 | 15 | /** 16 | * The .listReleases script. This script returns overall information about a Release 17 | * @param releaseClient the Public API {@link ReleasesClient} 18 | */ 19 | export default function listReleases(releaseClient: ReleasesClient): ListReleasesScript { 20 | return async (option) => { 21 | const result = await releaseClient.list(option.page); 22 | 23 | const releases = result.releases.map((release) => ({ 24 | sid: release.sid, 25 | configurationSid: release.configuration_sid, 26 | dateCreated: release.date_created, 27 | })); 28 | 29 | return { 30 | releases, 31 | meta: result.meta, 32 | }; 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /packages/flex-plugins-api-client/src/toolkit/scripts/release.ts: -------------------------------------------------------------------------------- 1 | import { ReleasesClient } from '../../clients'; 2 | import { Script } from '.'; 3 | 4 | export interface ReleaseOption { 5 | configurationSid: string; 6 | } 7 | 8 | export interface Release { 9 | releaseSid: string; 10 | configurationSid: string; 11 | dateCreated: string; 12 | } 13 | 14 | export type ReleaseScript = Script; 15 | 16 | /** 17 | * The .release script. This script will create a new Release 18 | * @param releaseClient the Public API {@link ReleasesClient} 19 | */ 20 | export default function release(releaseClient: ReleasesClient) { 21 | return async (option: ReleaseOption): Promise => { 22 | const createOption = { 23 | ConfigurationId: option.configurationSid, 24 | }; 25 | const releaseResource = await releaseClient.create(createOption); 26 | 27 | return { 28 | releaseSid: releaseResource.sid, 29 | configurationSid: releaseResource.configuration_sid, 30 | dateCreated: releaseResource.date_created, 31 | }; 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /packages/flex-plugins-api-client/src/toolkit/tools/index.ts: -------------------------------------------------------------------------------- 1 | export { Difference, ConfigurationsDiff, findConfigurationsDiff, buildDiff } from './diff'; 2 | -------------------------------------------------------------------------------- /packages/flex-plugins-api-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "baseUrl": ".", 6 | "rootDir": "src", 7 | "esModuleInterop": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/flex-plugins-api-client/tsconfig.lint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": [ 4 | "node_modules", 5 | "dist" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /packages/flex-plugins-utils-env/README.md: -------------------------------------------------------------------------------- 1 | ![npm](https://img.shields.io/npm/v/flex-plugins-utils-env.svg?style=square) 2 | ![npm](https://img.shields.io/npm/dt/flex-plugins-utils-env.svg?style=square) 3 | [![NpmLicense](https://img.shields.io/npm/l/flex-plugins-utils-env.svg?style=square)](../../LICENSE) 4 | 5 | # Flex Plugin Utils env 6 | 7 | Utility for returning environment variables. It is an isomorphic JavaScript module. 8 | 9 | ## Methods 10 | 11 | ### isWin32 12 | 13 | Returns `true` if OS is Windows. It will return false if script is running on the browser. 14 | 15 | ### persistTerminal 16 | 17 | If the module is used on the server, it will set the environment variable to persist the terminal logs. 18 | 19 | ### isTerminalPersisted 20 | 21 | Whether the terminal should be persisted or not. Only works on the server. 22 | 23 | ### isQuiet 24 | 25 | Whether the scripts should be quiet (no logs). Only works on the server. 26 | 27 | ### isDebug 28 | 29 | Returns true if the log level is debug. Works on both the browser and the server. 30 | 31 | ### isTrace 32 | 33 | Returns true if the log level is trace. Works on both the browser and the server. 34 | 35 | ### getRegion 36 | 37 | Returns the region. Will use the process env if running on the server, otherwise uses the windows.location. 38 | 39 | ### isCI 40 | 41 | Returns true if script running in CI. Works on the server only. 42 | -------------------------------------------------------------------------------- /packages/flex-plugins-utils-env/jest.config.js: -------------------------------------------------------------------------------- 1 | const base = require('./../../jest.base.js'); 2 | const pkg = require('./package'); 3 | 4 | module.exports = { 5 | rootDir: '.', 6 | ...base(pkg), 7 | coverageThreshold: { 8 | global: { 9 | statements: 92, 10 | branches: 95, 11 | lines: 92, 12 | functions: 94, 13 | }, 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /packages/flex-plugins-utils-env/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@twilio/flex-plugins-utils-env", 3 | "version": "7.1.1", 4 | "description": "Flex Plugins Environments", 5 | "keywords": [ 6 | "flex", 7 | "flex plugins", 8 | "flex plugins env" 9 | ], 10 | "homepage": "https://github.com/twilio/flex-plugin-builder", 11 | "bugs": { 12 | "url": "https://github.com/twilio/flex-plugin-builder/issues" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/twilio/flex-plugin-builder.git" 17 | }, 18 | "license": "MIT", 19 | "author": "Flex Runtime ", 20 | "main": "dist/index.js", 21 | "types": "dist/index.d.ts", 22 | "directories": { 23 | "lib": "src", 24 | "test": "__tests__" 25 | }, 26 | "files": [ 27 | "dist" 28 | ], 29 | "scripts": { 30 | "prebuild": "npm run clean", 31 | "build": "cd ../.. && tsc -p \"packages/flex-plugins-utils-env/tsconfig.json\"", 32 | "clean": "rm -rf dist", 33 | "lint": "eslint --ext ts src/", 34 | "lint:fix": "npm run lint -- --fix", 35 | "test": "cd ../.. && jest packages/flex-plugins-utils-env --color", 36 | "test:watch": "cd ../.. && jest packages/flex-plugins-utils-env --watch --color" 37 | }, 38 | "dependencies": { 39 | "@twilio/flex-plugins-utils-exception": "7.1.1", 40 | "@types/lodash.get": "^4.4.6", 41 | "lodash.get": "^4.4.2" 42 | }, 43 | "publishConfig": { 44 | "access": "public" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/flex-plugins-utils-env/src/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-unused-modules */ 2 | 3 | export { default, default as env, Environment, Lifecycle, Region } from './lib/env'; 4 | -------------------------------------------------------------------------------- /packages/flex-plugins-utils-env/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "baseUrl": ".", 6 | "rootDir": "src", 7 | "esModuleInterop": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/flex-plugins-utils-env/tsconfig.lint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": [ 4 | "node_modules", 5 | "dist" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /packages/flex-plugins-utils-exception/README.md: -------------------------------------------------------------------------------- 1 | ![npm](https://img.shields.io/npm/v/flex-plugins-utils-exception.svg?style=square) 2 | ![npm](https://img.shields.io/npm/dt/flex-plugins-utils-exception.svg?style=square) 3 | [![NpmLicense](https://img.shields.io/npm/l/flex-plugins-utils-exception.svg?style=square)](../../LICENSE) 4 | 5 | # Flex Plugin Utils Exception 6 | 7 | Exception error classes 8 | -------------------------------------------------------------------------------- /packages/flex-plugins-utils-exception/jest.config.js: -------------------------------------------------------------------------------- 1 | const base = require('./../../jest.base.js'); 2 | const pkg = require('./package'); 3 | 4 | module.exports = { 5 | rootDir: '.', 6 | ...base(pkg), 7 | coverageThreshold: { 8 | global: { 9 | statements: 97, 10 | branches: 70, 11 | lines: 96, 12 | functions: 96, 13 | }, 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /packages/flex-plugins-utils-exception/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@twilio/flex-plugins-utils-exception", 3 | "version": "7.1.1", 4 | "description": "Flex Plugins Exceptions", 5 | "keywords": [ 6 | "flex", 7 | "flex plugins", 8 | "flex plugins exception" 9 | ], 10 | "homepage": "https://github.com/twilio/flex-plugin-builder", 11 | "bugs": { 12 | "url": "https://github.com/twilio/flex-plugin-builder/issues" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/twilio/flex-plugin-builder.git" 17 | }, 18 | "license": "MIT", 19 | "author": "Flex Runtime ", 20 | "main": "dist/index.js", 21 | "types": "dist/index.d.ts", 22 | "directories": { 23 | "lib": "src", 24 | "test": "__tests__" 25 | }, 26 | "files": [ 27 | "dist" 28 | ], 29 | "scripts": { 30 | "prebuild": "npm run clean", 31 | "build": "cd ../.. && tsc -p \"packages/flex-plugins-utils-exception/tsconfig.json\"", 32 | "clean": "rm -rf dist", 33 | "lint": "eslint --ext ts src/", 34 | "lint:fix": "npm run lint -- --fix", 35 | "test": "cd ../.. && jest packages/flex-plugins-utils-exception --color", 36 | "test:watch": "cd ../.. && jest packages/flex-plugins-utils-exception --watch --color" 37 | }, 38 | "publishConfig": { 39 | "access": "public" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/flex-plugins-utils-exception/src/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-unused-modules */ 2 | 3 | export { default as TwilioError } from './lib/TwilioError'; 4 | export { default as TwilioApiError } from './lib/TwilioApiError'; 5 | export { default as NotImplementedError } from './lib/NotImplementedError'; 6 | export { default as TwilioCliError } from './lib/TwilioCliError'; 7 | -------------------------------------------------------------------------------- /packages/flex-plugins-utils-exception/src/lib/NotImplementedError.ts: -------------------------------------------------------------------------------- 1 | import TwilioCliError from './TwilioCliError'; 2 | 3 | export default class NotImplementedError extends TwilioCliError { 4 | constructor() { 5 | super('Abstract method must be implemented'); 6 | 7 | Object.setPrototypeOf(this, NotImplementedError.prototype); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/flex-plugins-utils-exception/src/lib/TwilioApiError.ts: -------------------------------------------------------------------------------- 1 | import TwilioError from './TwilioError'; 2 | 3 | /** 4 | *w 5 | * A Twilio REST API Error response 6 | * @link https://www.twilio.com/docs/usage/twilios-response#response-formats-exceptions 7 | */ 8 | export default class TwilioApiError extends TwilioError { 9 | public readonly code: number; 10 | 11 | public readonly message: string; 12 | 13 | public readonly moreInfo?: string; 14 | 15 | public readonly status: number; 16 | 17 | constructor(code: number, message: string, status: number, moreInfo?: string) { 18 | super(message); 19 | 20 | this.code = code; 21 | this.message = message; 22 | this.moreInfo = moreInfo; 23 | this.status = status; 24 | 25 | Object.setPrototypeOf(this, TwilioApiError.prototype); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/flex-plugins-utils-exception/src/lib/TwilioCliError.ts: -------------------------------------------------------------------------------- 1 | import TwilioError from './TwilioError'; 2 | 3 | export default class TwilioCliError extends TwilioError { 4 | constructor(msg?: string) { 5 | super(msg); 6 | 7 | Object.setPrototypeOf(this, TwilioCliError.prototype); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/flex-plugins-utils-exception/src/lib/TwilioError.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, import/no-unused-modules 2 | export type Constructable = new (...args: any[]) => T; 3 | 4 | /** 5 | * Base class for all errors generated by the script 6 | */ 7 | export default class TwilioError extends Error { 8 | constructor(msg?: string) { 9 | super(msg); 10 | 11 | Object.setPrototypeOf(this, TwilioError.prototype); 12 | } 13 | 14 | /** 15 | * Returns whether this is the instance of the passed class 16 | * @param klass the error class to test for 17 | */ 18 | public instanceOf = (klass: Constructable): boolean => { 19 | // eslint-disable-next-line consistent-this, @typescript-eslint/no-this-alias 20 | let instance = this; 21 | while (instance && instance !== Object.prototype) { 22 | if (!instance || !instance.constructor || !instance.constructor.name) { 23 | return false; 24 | } 25 | 26 | if (klass.name === instance.constructor.name) { 27 | return true; 28 | } 29 | 30 | instance = Object.getPrototypeOf(instance); 31 | } 32 | 33 | return false; 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /packages/flex-plugins-utils-exception/src/lib/__tests__/NotImplementedError.test.ts: -------------------------------------------------------------------------------- 1 | import { TwilioError, NotImplementedError, TwilioCliError } from '../..'; 2 | 3 | describe('NotImplementedError', () => { 4 | beforeEach(() => { 5 | jest.resetAllMocks(); 6 | }); 7 | 8 | it('should extend NotImplementedError', () => { 9 | expect(new NotImplementedError()).toBeInstanceOf(NotImplementedError); 10 | expect(new NotImplementedError()).toBeInstanceOf(TwilioError); 11 | expect(new NotImplementedError()).toBeInstanceOf(TwilioCliError); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /packages/flex-plugins-utils-exception/src/lib/__tests__/TwilioApiError.test.ts: -------------------------------------------------------------------------------- 1 | import TwilioError from '../TwilioError'; 2 | import TwilioApiError from '../TwilioApiError'; 3 | 4 | describe('TwilioApiError', () => { 5 | const errMsg = 'the-message'; 6 | 7 | beforeEach(() => { 8 | jest.resetAllMocks(); 9 | }); 10 | 11 | it('should extend TwilioError', () => { 12 | expect(new TwilioApiError(123, 'message', 123)).toBeInstanceOf(TwilioError); 13 | }); 14 | 15 | it('should be of its instance', () => { 16 | expect(new TwilioApiError(123, 'message', 123)).toBeInstanceOf(TwilioApiError); 17 | }); 18 | 19 | it('should have required properties set', () => { 20 | const err = new TwilioApiError(456, errMsg, 400); 21 | 22 | expect(err.status).toEqual(400); 23 | expect(err.code).toEqual(456); 24 | expect(err.message).toEqual(errMsg); 25 | expect(err.moreInfo).toBeUndefined(); 26 | }); 27 | 28 | it('should have all properties set', () => { 29 | const err = new TwilioApiError(456, errMsg, 400, 'more-info'); 30 | 31 | expect(err.status).toEqual(400); 32 | expect(err.code).toEqual(456); 33 | expect(err.message).toEqual(errMsg); 34 | expect(err.moreInfo).toEqual('more-info'); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /packages/flex-plugins-utils-exception/src/lib/__tests__/TwilioCliError.test.ts: -------------------------------------------------------------------------------- 1 | import { TwilioError, TwilioCliError } from '../..'; 2 | 3 | describe('TwilioCliError', () => { 4 | beforeEach(() => { 5 | jest.resetAllMocks(); 6 | }); 7 | 8 | it('should extend TwilioCliError', () => { 9 | expect(new TwilioCliError()).toBeInstanceOf(TwilioError); 10 | expect(new TwilioCliError()).toBeInstanceOf(TwilioCliError); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/flex-plugins-utils-exception/src/lib/__tests__/TwilioError.test.ts: -------------------------------------------------------------------------------- 1 | import TwilioError from '../TwilioError'; 2 | import { TwilioApiError } from '../..'; 3 | 4 | describe('TwilioError', () => { 5 | beforeEach(() => { 6 | jest.resetAllMocks(); 7 | }); 8 | 9 | it('should be the error', () => { 10 | expect(new TwilioError()).toBeInstanceOf(TwilioError); 11 | }); 12 | 13 | it('should return true for instanceOf', () => { 14 | const apiError = new TwilioApiError(123, 'test', 123); 15 | const error = new TwilioError(); 16 | 17 | expect(apiError.instanceOf(TwilioApiError)).toEqual(true); 18 | expect(apiError.instanceOf(TwilioError)).toEqual(true); 19 | expect(apiError.instanceOf(Error)).toEqual(true); 20 | 21 | expect(error.instanceOf(TwilioError)).toEqual(true); 22 | expect(error.instanceOf(Error)).toEqual(true); 23 | }); 24 | 25 | it('should return false for instanceOf', () => { 26 | class Foo extends Error {} 27 | 28 | const apiError = new TwilioApiError(123, 'test', 123); 29 | const error = new TwilioError(); 30 | const noConstuctorError = new TwilioError(); 31 | 32 | noConstuctorError.constructor = jest.fn(); 33 | 34 | expect(apiError.instanceOf(Foo)).toEqual(false); 35 | expect(error.instanceOf(TwilioApiError)).toEqual(false); 36 | expect(noConstuctorError.instanceOf(Foo)).toEqual(false); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /packages/flex-plugins-utils-exception/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "baseUrl": ".", 6 | "rootDir": "src", 7 | "esModuleInterop": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/flex-plugins-utils-exception/tsconfig.lint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": [ 4 | "node_modules", 5 | "dist" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /packages/plugin-flex/bin/prepack: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | ../../node_modules/.bin/oclif-dev manifest 6 | npm run replace-doc 7 | -------------------------------------------------------------------------------- /packages/plugin-flex/docs/README.md: -------------------------------------------------------------------------------- 1 | @twilio-labs/plugin-flex 2 | ======================== 3 | 4 | Twilio CLI plugin to interact with the [Flex Plugin Builder](https://github.com/twilio/flex-plugin-builder) 5 | 6 | This plugin adds functionality to the [Twilio CLI](https://github.com/twilio/twilio-cli) to locally develop, 7 | build and deploy [Twilio Flex plugins](https://www.twilio.com/docs/flex/plugin-builder); it uses the [Flex Plugin Builder](https://github.com/twilio/flex-plugin-builder). 8 | 9 | 10 | * [Requirements](#requirements) 11 | * [Usage](#usage) 12 | * [Commands](#commands) 13 | 14 | 15 | # Requirements 16 | 17 | ## Install the Twilio CLI 18 | 19 | Via `npm` or `yarn`: 20 | 21 | ```sh-session 22 | $ npm install -g twilio-cli 23 | $ yarn global add twilio-cli 24 | ``` 25 | 26 | Via `homebrew`: 27 | 28 | ```sh-session 29 | $ brew tap twilio/brew && brew install twilio 30 | ``` 31 | 32 | # Usage 33 | 34 | ```sh-session 35 | $ twilio plugins:install @twilio-labs/plugin-flex 36 | 37 | $ twilio --help flex 38 | 39 | USAGE 40 | $ twilio flex 41 | ... 42 | ``` 43 | 44 | # Commands 45 | 46 | 47 | -------------------------------------------------------------------------------- /packages/plugin-flex/jest.config.js: -------------------------------------------------------------------------------- 1 | const base = require('./../../jest.base'); 2 | const pkg = require('./package'); 3 | 4 | pkg.name = 'plugin-flex'; 5 | 6 | module.exports = { 7 | rootDir: '.', 8 | ...base(pkg), 9 | globals: { 10 | 'ts-jest': { 11 | tsconfig: `/tsconfig.json`, 12 | }, 13 | }, 14 | coverageThreshold: { 15 | global: { 16 | statements: 63, 17 | branches: 61, 18 | lines: 64, 19 | functions: 50, 20 | }, 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /packages/plugin-flex/src/__tests__/commands/flex/plugins/create-configuration.test.ts: -------------------------------------------------------------------------------- 1 | import FlexPluginsCreateConfiguration from '../../../../commands/flex/plugins/create-configuration'; 2 | import CreateConfiguration from '../../../../sub-commands/create-configuration'; 3 | import FlexPlugin from '../../../../sub-commands/flex-plugin'; 4 | import createTest from '../../../framework'; 5 | 6 | describe('Commands/FlexPluginsCreateConfiguration', () => { 7 | it('should have own flags', () => { 8 | expect(FlexPluginsCreateConfiguration.flags).not.toBeSameObject(CreateConfiguration.flags); 9 | expect(FlexPluginsCreateConfiguration.flags).not.toBeSameObject(FlexPlugin.flags); 10 | }); 11 | 12 | it('should get topic name', async () => { 13 | const cmd = await createTest(FlexPluginsCreateConfiguration)(); 14 | 15 | expect(cmd.getTopicName()).toContain(FlexPluginsCreateConfiguration.topicName); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/plugin-flex/src/__tests__/commands/flex/plugins/describe/plugin-version.test.ts: -------------------------------------------------------------------------------- 1 | import { TwilioCliError } from '@twilio/flex-dev-utils'; 2 | 3 | import FlexPluginsDescribePluginVersion from '../../../../../commands/flex/plugins/describe/plugin-version'; 4 | import FlexPlugin from '../../../../../sub-commands/flex-plugin'; 5 | import InformationFlexPlugin from '../../../../../sub-commands/information-flex-plugin'; 6 | import createTest from '../../../../framework'; 7 | 8 | describe('Commands/Describe/FlexPluginsDescribePluginVersion', () => { 9 | it('should have own flags', () => { 10 | expect(FlexPluginsDescribePluginVersion.flags).not.toBeSameObject(InformationFlexPlugin.flags); 11 | expect(FlexPluginsDescribePluginVersion.flags).not.toBeSameObject(FlexPlugin.flags); 12 | }); 13 | 14 | it('should set parsed flags', async () => { 15 | const cmd = await createTest(FlexPluginsDescribePluginVersion)('--name', 'plugin-one', '--version', '1.0.2'); 16 | await cmd.init(); 17 | expect(cmd._flags).toBeDefined(); 18 | }); 19 | 20 | it('should get topic name', async () => { 21 | const cmd = await createTest(FlexPluginsDescribePluginVersion)('--name', 'plugin-one', '--version', '1.0.2'); 22 | 23 | expect(cmd.getTopicName()).toContain(FlexPluginsDescribePluginVersion.topicName); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/plugin-flex/src/__tests__/commands/flex/plugins/describe/plugin.test.ts: -------------------------------------------------------------------------------- 1 | import { TwilioCliError } from '@twilio/flex-dev-utils'; 2 | 3 | import FlexPluginsDescribePlugin from '../../../../../commands/flex/plugins/describe/plugin'; 4 | import FlexPlugin from '../../../../../sub-commands/flex-plugin'; 5 | import InformationFlexPlugin from '../../../../../sub-commands/information-flex-plugin'; 6 | import createTest from '../../../../framework'; 7 | 8 | describe('Commands/Describe/FlexPluginsDescribePlugin', () => { 9 | it('should have own flags', () => { 10 | expect(FlexPluginsDescribePlugin.flags).not.toBeSameObject(InformationFlexPlugin.flags); 11 | expect(FlexPluginsDescribePlugin.flags).not.toBeSameObject(FlexPlugin.flags); 12 | }); 13 | 14 | it('should set parsed flags', async () => { 15 | const cmd = await createTest(FlexPluginsDescribePlugin)('--name', 'plugin-one'); 16 | await cmd.init(); 17 | expect(cmd._flags).toBeDefined(); 18 | }); 19 | 20 | it('should get topic name', async () => { 21 | const cmd = await createTest(FlexPluginsDescribePlugin)('--name', 'plugin-one'); 22 | 23 | expect(cmd.getTopicName()).toContain(FlexPluginsDescribePlugin.topicName); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/plugin-flex/src/__tests__/commands/flex/plugins/describe/release.test.ts: -------------------------------------------------------------------------------- 1 | import { TwilioCliError } from '@twilio/flex-dev-utils'; 2 | 3 | import FlexPluginsDescribeRelease from '../../../../../commands/flex/plugins/describe/release'; 4 | import FlexPlugin from '../../../../../sub-commands/flex-plugin'; 5 | import InformationFlexPlugin from '../../../../../sub-commands/information-flex-plugin'; 6 | import createTest from '../../../../framework'; 7 | 8 | describe('Commands/Describe/FlexPluginsDescribeRelease', () => { 9 | const sid = '1234567890'; 10 | const createCommand = async (...args: string[]): Promise => { 11 | const cmd = await createTest(FlexPluginsDescribeRelease)(...args); 12 | await cmd.init(); 13 | return cmd; 14 | }; 15 | 16 | it('should have own flags', () => { 17 | expect(FlexPluginsDescribeRelease.flags).not.toBeSameObject(InformationFlexPlugin.flags); 18 | expect(FlexPluginsDescribeRelease.flags).not.toBeSameObject(FlexPlugin.flags); 19 | }); 20 | 21 | it('should set parsed flags', async () => { 22 | const cmd = await createCommand('--sid', sid); 23 | expect(cmd._flags).toBeDefined(); 24 | }); 25 | 26 | it('should get topic name', async () => { 27 | const cmd = await createCommand('--sid', sid); 28 | 29 | expect(cmd.getTopicName()).toContain(FlexPluginsDescribeRelease.topicName); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /packages/plugin-flex/src/__tests__/commands/flex/plugins/list/configurations.test.ts: -------------------------------------------------------------------------------- 1 | import FlexPluginsListConfigurations from '../../../../../commands/flex/plugins/list/configurations'; 2 | import FlexPlugin from '../../../../../sub-commands/flex-plugin'; 3 | import InformationFlexPlugin from '../../../../../sub-commands/information-flex-plugin'; 4 | import createTest from '../../../../framework'; 5 | 6 | describe('Commands/List/FlexPluginsListConfigurations', () => { 7 | it('should have own flags', () => { 8 | expect(FlexPluginsListConfigurations.flags).not.toBeSameObject(InformationFlexPlugin.flags); 9 | expect(FlexPluginsListConfigurations.flags).not.toBeSameObject(FlexPlugin.flags); 10 | }); 11 | 12 | it('should get topic name', async () => { 13 | const cmd = await createTest(FlexPluginsListConfigurations)(); 14 | 15 | expect(cmd.getTopicName()).toContain(FlexPluginsListConfigurations.topicName); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/plugin-flex/src/__tests__/commands/flex/plugins/list/plugin-versions.test.ts: -------------------------------------------------------------------------------- 1 | import { TwilioCliError } from '@twilio/flex-dev-utils'; 2 | 3 | import FlexPluginsListPluginVersions from '../../../../../commands/flex/plugins/list/plugin-versions'; 4 | import FlexPlugin from '../../../../../sub-commands/flex-plugin'; 5 | import InformationFlexPlugin from '../../../../../sub-commands/information-flex-plugin'; 6 | import createTest from '../../../../framework'; 7 | 8 | describe('Commands/List/FlexPluginsListPluginVersions', () => { 9 | it('should have own flags', () => { 10 | expect(FlexPluginsListPluginVersions.flags).not.toBeSameObject(InformationFlexPlugin.flags); 11 | expect(FlexPluginsListPluginVersions.flags).not.toBeSameObject(FlexPlugin.flags); 12 | }); 13 | 14 | it('should set parsed flags', async () => { 15 | const cmd = await createTest(FlexPluginsListPluginVersions)('--name', 'plugin-one'); 16 | await cmd.init(); 17 | expect(cmd._flags).toBeDefined(); 18 | }); 19 | 20 | it('should get topic name', async () => { 21 | const cmd = await createTest(FlexPluginsListPluginVersions)('--name', 'plugin-one'); 22 | 23 | expect(cmd.getTopicName()).toContain(FlexPluginsListPluginVersions.topicName); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/plugin-flex/src/__tests__/commands/flex/plugins/list/plugins.test.ts: -------------------------------------------------------------------------------- 1 | import FlexPluginsListPlugins from '../../../../../commands/flex/plugins/list/plugins'; 2 | import FlexPlugin from '../../../../../sub-commands/flex-plugin'; 3 | import InformationFlexPlugin from '../../../../../sub-commands/information-flex-plugin'; 4 | import createTest from '../../../../framework'; 5 | 6 | describe('Commands/List/FlexPluginsListPlugins', () => { 7 | it('should have own flags', () => { 8 | expect(FlexPluginsListPlugins.flags).not.toBeSameObject(InformationFlexPlugin.flags); 9 | expect(FlexPluginsListPlugins.flags).not.toBeSameObject(FlexPlugin.flags); 10 | }); 11 | 12 | it('should get topic name', async () => { 13 | const cmd = await createTest(FlexPluginsListPlugins)(); 14 | 15 | expect(cmd.getTopicName()).toContain(FlexPluginsListPlugins.topicName); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/plugin-flex/src/__tests__/commands/flex/plugins/list/releases.test.ts: -------------------------------------------------------------------------------- 1 | import FlexPluginsListPlugins from '../../../../../commands/flex/plugins/list/releases'; 2 | import FlexPlugin from '../../../../../sub-commands/flex-plugin'; 3 | import InformationFlexPlugin from '../../../../../sub-commands/information-flex-plugin'; 4 | import createTest from '../../../../framework'; 5 | 6 | describe('Commands/List/FlexPluginsListPlugins', () => { 7 | it('should have own flags', () => { 8 | expect(FlexPluginsListPlugins.flags).not.toBeSameObject(InformationFlexPlugin.flags); 9 | expect(FlexPluginsListPlugins.flags).not.toBeSameObject(FlexPlugin.flags); 10 | }); 11 | 12 | it('should get topic name', async () => { 13 | const cmd = await createTest(FlexPluginsListPlugins)(); 14 | 15 | expect(cmd.getTopicName()).toContain(FlexPluginsListPlugins.topicName); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/plugin-flex/src/__tests__/commands/flex/plugins/validate.test.ts: -------------------------------------------------------------------------------- 1 | import createTest from '../../../framework'; 2 | import FlexPluginsValidate from '../../../../commands/flex/plugins/validate'; 3 | import FlexPlugin from '../../../../sub-commands/flex-plugin'; 4 | 5 | describe('Validate', () => { 6 | beforeEach(() => { 7 | jest.resetAllMocks(); 8 | }); 9 | 10 | const createCommand = async (...args: string[]): Promise => { 11 | const cmd = await createTest(FlexPluginsValidate)(...args); 12 | await cmd.init(); 13 | return cmd; 14 | }; 15 | 16 | it('should have own flags', () => { 17 | expect(FlexPluginsValidate.flags).not.toBeSameObject(FlexPlugin.flags); 18 | }); 19 | 20 | it('should run validate script', async () => { 21 | const cmd = await createCommand(); 22 | 23 | jest.spyOn(cmd, 'runScript').mockReturnThis(); 24 | 25 | await cmd.doRun(); 26 | 27 | expect(cmd.runScript).toHaveBeenCalledTimes(1); 28 | expect(cmd.runScript).toHaveBeenCalledWith('validate'); 29 | }); 30 | 31 | it('should have compatibility set', async () => { 32 | const cmd = await createCommand(); 33 | expect(cmd.checkCompatibility).toEqual(true); 34 | }); 35 | 36 | it('should get topic name', async () => { 37 | const cmd = await createCommand(); 38 | 39 | expect(cmd.getTopicName()).toContain(FlexPluginsValidate.topicName); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /packages/plugin-flex/src/commands/flex/plugins/build.ts: -------------------------------------------------------------------------------- 1 | import { createDescription } from '../../../utils/general'; 2 | import FlexPlugin from '../../../sub-commands/flex-plugin'; 3 | 4 | const baseFlags = { ...FlexPlugin.flags }; 5 | // @ts-ignore 6 | delete baseFlags.json; 7 | 8 | /** 9 | * Builds the the plugin bundle 10 | */ 11 | export default class FlexPluginsBuild extends FlexPlugin { 12 | static topicName = 'flex:plugins:build'; 13 | 14 | static description = createDescription(FlexPluginsBuild.topic.description, true); 15 | 16 | static flags = { 17 | ...baseFlags, 18 | }; 19 | 20 | /** 21 | * @override 22 | */ 23 | async doRun(): Promise { 24 | process.env.PERSIST_TERMINAL = 'true'; 25 | await this.runScript('pre-script-check'); 26 | await this.runScript('build'); 27 | } 28 | 29 | /** 30 | * @override 31 | */ 32 | get checkCompatibility(): boolean { 33 | return true; 34 | } 35 | 36 | /** 37 | * @override 38 | */ 39 | getTopicName(): string { 40 | return FlexPluginsBuild.topicName; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/plugin-flex/src/commands/flex/plugins/list/releases.ts: -------------------------------------------------------------------------------- 1 | import { ListReleases } from '@twilio/flex-plugins-api-client'; 2 | 3 | import { createDescription } from '../../../../utils/general'; 4 | import InformationFlexPlugin from '../../../../sub-commands/information-flex-plugin'; 5 | 6 | /** 7 | * Lists the Flex Plugin Releases 8 | */ 9 | export default class FlexPluginsListPlugins extends InformationFlexPlugin { 10 | static topicName = 'flex:plugins:list:releases'; 11 | 12 | static description = createDescription(FlexPluginsListPlugins.topic.description, false); 13 | 14 | static flags = { 15 | ...InformationFlexPlugin.flags, 16 | }; 17 | 18 | /** 19 | * @override 20 | */ 21 | async getResource(): Promise { 22 | const result = await this.pluginsApiToolkit.listReleases({}); 23 | 24 | return result.releases; 25 | } 26 | 27 | /** 28 | * @override 29 | */ 30 | /* c8 ignore next */ 31 | notFound(): void { 32 | this._logger.info(`!!No releases where not found.!!`); 33 | } 34 | 35 | /** 36 | * @override 37 | */ 38 | /* c8 ignore next */ 39 | print(releases: ListReleases[]): void { 40 | releases.forEach((release) => { 41 | this.printVersion(release.sid); 42 | this.printPretty(release); 43 | this._logger.newline(); 44 | }); 45 | } 46 | 47 | /** 48 | * @override 49 | */ 50 | getTopicName(): string { 51 | return FlexPluginsListPlugins.topicName; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /packages/plugin-flex/src/commands/flex/plugins/test.ts: -------------------------------------------------------------------------------- 1 | import { createDescription } from '../../../utils/general'; 2 | import FlexPlugin, { ConfigData, SecureStorage } from '../../../sub-commands/flex-plugin'; 3 | 4 | const baseFlags = { ...FlexPlugin.flags }; 5 | // @ts-ignore 6 | delete baseFlags.json; 7 | 8 | /** 9 | * Builds the the plugin bundle 10 | */ 11 | export default class FlexPluginsTest extends FlexPlugin { 12 | static topicName = 'flex:plugins:test'; 13 | 14 | static description = createDescription(FlexPluginsTest.topic.description, true); 15 | 16 | static flags = { 17 | ...baseFlags, 18 | }; 19 | 20 | constructor(argv: string[], config: ConfigData, secureStorage: SecureStorage) { 21 | super(argv, config, secureStorage, { strict: false, runTelemetryAsync: false }); 22 | } 23 | 24 | /** 25 | * @override 26 | */ 27 | async doRun(): Promise { 28 | process.env.PERSIST_TERMINAL = 'true'; 29 | await this.runScript('pre-script-check'); 30 | await this.runScript('test', ['--env=jsdom', ...this.internalScriptArgs]); 31 | } 32 | 33 | /** 34 | * @override 35 | */ 36 | get checkCompatibility(): boolean { 37 | return true; 38 | } 39 | 40 | /** 41 | * @override 42 | */ 43 | getTopicName(): string { 44 | return FlexPluginsTest.topicName; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/plugin-flex/src/prints/archiveResource.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from '@twilio/flex-dev-utils'; 2 | 3 | const archivedSuccessfully = 4 | (logger: Logger) => 5 | (name: string): void => { 6 | logger.info(`++**${name}** was successfully archived.++`); 7 | }; 8 | 9 | const archivedFailed = 10 | (logger: Logger) => 11 | (name: string): void => { 12 | logger.info(`--Could not archive **${name}**; please try again later.--`); 13 | }; 14 | 15 | const alreadyArchived = 16 | (logger: Logger) => 17 | (name: string, message: string): void => { 18 | logger.info(`!!Cannot archive ${name} because ${message.toLowerCase()}!!`); 19 | }; 20 | 21 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types 22 | export default (logger: Logger) => ({ 23 | archivedSuccessfully: archivedSuccessfully(logger), 24 | archivedFailed: archivedFailed(logger), 25 | alreadyArchived: alreadyArchived(logger), 26 | }); 27 | -------------------------------------------------------------------------------- /packages/plugin-flex/src/prints/flexPlugin.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from '@twilio/flex-dev-utils'; 2 | 3 | /** 4 | * Prints error about incompatibility 5 | */ 6 | const incompatibleVersion = (logger: Logger) => (name: string, version: number | null) => { 7 | logger.error(`The plugin ${name} version (v${version}) is not compatible with this CLI command.`); 8 | logger.newline(); 9 | logger.info('Run {{$ twilio flex:plugins:upgrade-plugin \\-\\-install}} to upgrade your plugin.'); 10 | }; 11 | 12 | /** 13 | * Prints warning about new version of OpenSSL in Node v18 14 | */ 15 | const openSSLWarning = (logger: Logger) => () => { 16 | logger.newline(); 17 | logger.warning( 18 | 'WARNING: If you encounter this error: {{ERR-OSSL-EVP-UNSUPPORTED}}, which happens due to incompatibility between newer version of OpenSSL and Node v18, run the following command:', 19 | ); 20 | logger.newline(); 21 | logger.info('For MacOS & Linux: Run {{$ export NODE_OPTIONS=\\-\\-openssl-legacy-provider}}'); 22 | logger.newline(); 23 | logger.info('For Windows: Run {{$ set NODE_OPTIONS=\\-\\-openssl-legacy-provider}}'); 24 | logger.newline(); 25 | }; 26 | 27 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types 28 | export default (logger: Logger) => ({ 29 | incompatibleVersion: incompatibleVersion(logger), 30 | openSSLWarning: openSSLWarning(logger), 31 | }); 32 | -------------------------------------------------------------------------------- /packages/plugin-flex/src/prints/index.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from '@twilio/flex-dev-utils'; 2 | 3 | import upgradePlugin from './upgradePlugin'; 4 | import deploy from './deploy'; 5 | import release from './release'; 6 | import flexPlugin from './flexPlugin'; 7 | import archiveResource from './archiveResource'; 8 | 9 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types 10 | export default (logger: Logger) => { 11 | return { 12 | upgradePlugin: upgradePlugin(logger), 13 | deploy: deploy(logger), 14 | release: release(logger), 15 | flexPlugin: flexPlugin(logger), 16 | archiveResource: archiveResource(logger), 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /packages/plugin-flex/src/prints/release.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from '@twilio/flex-dev-utils'; 2 | 3 | /** 4 | * Successful release 5 | */ 6 | const releaseSuccessful = (logger: Logger) => (configurationSid: string) => { 7 | logger.newline(); 8 | logger.success(`🚀 Configuration **${configurationSid}** was successfully enabled.`); 9 | logger.newline(); 10 | 11 | logger.info('**Next Steps:**'); 12 | logger.info('Visit https://flex.twilio.com/admin/plugins to see your plugin(s) live on Flex.'); 13 | logger.newline(); 14 | }; 15 | 16 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types 17 | export default (logger: Logger) => ({ 18 | releaseSuccessful: releaseSuccessful(logger), 19 | }); 20 | -------------------------------------------------------------------------------- /packages/plugin-flex/src/utils/config.ts: -------------------------------------------------------------------------------- 1 | import { readJsonFile } from '@twilio/flex-dev-utils/dist/fs'; 2 | 3 | import { Pkg } from '../sub-commands/flex-plugin'; 4 | 5 | export interface OClifTopic { 6 | description: string; 7 | flags: { 8 | [key: string]: string; 9 | }; 10 | args: { 11 | [key: string]: string; 12 | }; 13 | defaults: { 14 | [key: string]: string; 15 | }; 16 | } 17 | 18 | export interface OclifConfig { 19 | name: string; 20 | commands: string; 21 | bin: string; 22 | devPlugins: string[]; 23 | topics: { 24 | [key: string]: OClifTopic; 25 | }; 26 | } 27 | 28 | /** 29 | * Reads the topic information from package.json 30 | * @param topicName the topic name to read 31 | */ 32 | export const getTopic = (topicName: string): OClifTopic => { 33 | const pkg = readJsonFile(__dirname, '../../package.json'); 34 | if (!pkg?.oclif?.topics[topicName]) { 35 | return { 36 | description: 'No description available', 37 | flags: {}, 38 | args: {}, 39 | defaults: {}, 40 | }; 41 | } 42 | 43 | const topic = pkg.oclif.topics[topicName]; 44 | topic.flags = topic.flags || {}; 45 | topic.args = topic.args || {}; 46 | topic.defaults = topic.defaults || {}; 47 | 48 | return topic; 49 | }; 50 | -------------------------------------------------------------------------------- /packages/plugin-flex/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | /* c8 ignore start */ 2 | 3 | export { getTopic, OClifTopic, OclifConfig } from './config'; 4 | 5 | /* c8 ignore stop */ 6 | -------------------------------------------------------------------------------- /packages/plugin-flex/src/utils/strings.ts: -------------------------------------------------------------------------------- 1 | import startCase from 'lodash.startcase'; 2 | 3 | const KEY_CASE_OVERWRITE = { 4 | sid: 'SID', 5 | datecreated: 'Created', 6 | dateupdated: 'Updated', 7 | isprivate: 'Access', 8 | isactive: 'Status', 9 | url: 'URL', 10 | }; 11 | 12 | type NullUndefined = null | undefined | unknown; 13 | // eslint-disable-next-line @typescript-eslint/ban-types 14 | type Primitive = number | string | object; 15 | 16 | /** 17 | * Converts a string from camelCase to Sentence Case 18 | * @param key 19 | */ 20 | export const toSentenceCase = (key: string): string => { 21 | if (key.toLowerCase() in KEY_CASE_OVERWRITE) { 22 | return KEY_CASE_OVERWRITE[key.toLowerCase()]; 23 | } 24 | 25 | const split = startCase(key).split(' '); 26 | if (split.length <= 1) { 27 | return split.join(' '); 28 | } 29 | 30 | return split.map(toSentenceCase).join(' '); 31 | }; 32 | 33 | /** 34 | * Returns true if value is null or undefined 35 | * @param value the value to check 36 | */ 37 | export const isNullOrUndefined = (value?: NullUndefined | Primitive): boolean => value === undefined || value === null; 38 | -------------------------------------------------------------------------------- /packages/plugin-flex/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "baseUrl": ".", 6 | "rootDir": "src", 7 | "esModuleInterop": true, 8 | "target": "es2017" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/plugin-flex/tsconfig.lint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": [ 4 | "node_modules", 5 | "dist" 6 | ] 7 | } 8 | 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "baseUrl": ".", 5 | "rootDir": "src", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "lib": [ 9 | "es6", 10 | "dom" 11 | ], 12 | "sourceMap": true, 13 | "jsx": "react", 14 | "moduleResolution": "node", 15 | "forceConsistentCasingInFileNames": true, 16 | "strict": true, 17 | "suppressImplicitAnyIndexErrors": true, 18 | "noUnusedLocals": false, 19 | "skipLibCheck": true, 20 | "declaration": true, 21 | "resolveJsonModule": true, 22 | "esModuleInterop": true, 23 | "downlevelIteration": true, 24 | "types" : [ 25 | "node", 26 | "jest" 27 | ], 28 | }, 29 | "exclude": [ 30 | "**/__mocks__/*", 31 | "**/__tests__/*", 32 | "**/templates/*", 33 | "node_modules", 34 | "**/*.test.ts", 35 | "**/*.spec.ts", 36 | "dist" 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /tsconfig.lint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": [ 4 | "node_modules", 5 | "dist" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "files": [ 4 | "./packages/flex-plugin-utils-jest/dist/index.d.ts" 5 | ], 6 | "compilerOptions": { 7 | "noImplicitAny": false, 8 | } 9 | } 10 | --------------------------------------------------------------------------------