├── .editorconfig ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ └── bug_report.md ├── actions │ ├── copyDocker │ │ └── action.yml │ └── tagDocker │ │ └── action.yml ├── pull_request_template.md └── workflows │ ├── buildPackages.yml │ ├── cherry-picker.yml │ ├── issue-label-analysis.yml │ ├── promotePackage.yml │ ├── pull-request-task-checker.yml │ ├── release-notes-generator.yml │ ├── release.yml │ ├── review.yml │ ├── sfpowerscripts-build-docker.yml │ ├── sfpowerscripts-copy-docker.yml │ ├── sfpowerscripts-promote-docker.yml │ └── tagDockerImage.yml ├── .gitignore ├── .prettierignore ├── .prettierrc.json ├── .vscode └── settings.json ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── Third Party Notices.md ├── decision records ├── 001-sfpowerscripts-artifact-customsettings.md ├── 002-parallel-development-process.md ├── 003-dependency-manager.md ├── 004-cli-to-libs.md ├── 005-package-dependency-version-resolution.md ├── 006-field-history-tracking-resolution.md ├── 007-picklist-support.md ├── 008-diff-package-type.md ├── 009-migrating from-plugin.md ├── 010-ship-show-ask-model.md ├── artifacts │ └── 001-artifact-version-duplication.md ├── build │ └── 001-transitive-dependency-resolver-solution.md ├── deployments │ ├── 001-aliasified-data-sourcepackages.md │ └── 002-enable-compile-on-deploy.md ├── prepare │ ├── 001-prepare-source-tracking.md │ ├── 002-prepare-daisy-chaining.md │ └── 003-prepare-custom-script-executer.md ├── release │ ├── 001-release.md │ ├── 002-release-installing-packages.md │ ├── 003-release-autorollback.md │ ├── 004-Identifying-packages-in-npm.md │ ├── 005-cutting-release-branch.md │ └── 006-release-defn-generator.md └── validate │ ├── 001-automated-apex-testing-retry.md │ ├── 002-fast-feedback.md │ ├── 003-individual-validation-mode.md │ └── 004-validate-by-release-config.md ├── dockerfiles ├── sfp-lite.Dockerfile └── sfpowerscripts.Dockerfile ├── lerna.json ├── nx.json ├── package.json ├── packages ├── apexlink │ ├── README.md │ ├── jars │ │ ├── antlr4-runtime-4.8-1.jar │ │ ├── apex-parser-3.0.0.jar │ │ ├── apexlink-2.3.7.jar │ │ ├── geny_2.13-0.6.2.jar │ │ ├── pkgforce_2.13-2.3.7.jar │ │ ├── pom.xml │ │ ├── runforce-55.5.0.jar │ │ ├── scala-collection-compat_2.13-2.1.4.jar │ │ ├── scala-json-rpc-upickle-json-serializer_2.13-1.0.1.jar │ │ ├── scala-json-rpc_2.13-1.0.1.jar │ │ ├── scala-library-2.13.3.jar │ │ ├── scala-parallel-collections_2.13-1.0.0.jar │ │ ├── scala-reflect-2.13.3.jar │ │ ├── scala-xml_2.13-1.3.0.jar │ │ ├── ujson_2.13-1.2.0.jar │ │ ├── upack_2.13-1.2.0.jar │ │ ├── upickle-core_2.13-1.2.0.jar │ │ ├── upickle-implicits_2.13-1.2.0.jar │ │ └── upickle_2.13-1.2.0.jar │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── ApexDepedencyCheckImpl.ts │ │ └── index.ts │ ├── tests │ │ ├── ApexDependencyCheckImpl.test.ts │ │ └── resources │ │ │ ├── core-crm │ │ │ ├── README.md │ │ │ └── main │ │ │ │ └── objects │ │ │ │ └── Account │ │ │ │ └── fields │ │ │ │ ├── AccountNumber2__c.field-meta.xml │ │ │ │ ├── AccountNumber3__c.field-meta.xml │ │ │ │ └── AccountNumber__c.field-meta.xml │ │ │ └── feature-mgmt │ │ │ ├── core │ │ │ ├── main │ │ │ │ ├── classes │ │ │ │ │ ├── CustomMetadataFeatureSettingsProvider.cls │ │ │ │ │ ├── CustomMetadataFeatureSettingsProvider.cls-meta.xml │ │ │ │ │ ├── Feature.cls │ │ │ │ │ ├── Feature.cls-meta.xml │ │ │ │ │ ├── FeatureChecker.cls │ │ │ │ │ ├── FeatureChecker.cls-meta.xml │ │ │ │ │ ├── FeatureCheckerImplementation.cls │ │ │ │ │ ├── FeatureCheckerImplementation.cls-meta.xml │ │ │ │ │ ├── FeatureSetting.cls │ │ │ │ │ ├── FeatureSetting.cls-meta.xml │ │ │ │ │ ├── FeatureSettingsProvider.cls │ │ │ │ │ └── FeatureSettingsProvider.cls-meta.xml │ │ │ │ ├── layouts │ │ │ │ │ └── FeatureSetting__mdt-Feature Setting Layout.layout-meta.xml │ │ │ │ └── objects │ │ │ │ │ └── FeatureSetting__mdt │ │ │ │ │ ├── FeatureSetting__mdt.object-meta.xml │ │ │ │ │ └── fields │ │ │ │ │ └── Implementation__c.field-meta.xml │ │ │ └── test │ │ │ │ └── classes │ │ │ │ ├── CustMetadataFeatureSettingsProviderTest.cls │ │ │ │ ├── CustMetadataFeatureSettingsProviderTest.cls-meta.xml │ │ │ │ ├── FeatureCheckerImplementationTest.cls │ │ │ │ ├── FeatureCheckerImplementationTest.cls-meta.xml │ │ │ │ ├── FeatureSettingTest.cls │ │ │ │ └── FeatureSettingTest.cls-meta.xml │ │ │ └── features │ │ │ ├── main │ │ │ ├── classes │ │ │ │ ├── AlwaysDisabledFeature.cls │ │ │ │ ├── AlwaysDisabledFeature.cls-meta.xml │ │ │ │ ├── AlwaysEnabledFeature.cls │ │ │ │ ├── AlwaysEnabledFeature.cls-meta.xml │ │ │ │ ├── CustomMetadataEnabledFeature.cls │ │ │ │ ├── CustomMetadataEnabledFeature.cls-meta.xml │ │ │ │ ├── CustomPermissionEnabledFeature.cls │ │ │ │ └── CustomPermissionEnabledFeature.cls-meta.xml │ │ │ ├── layouts │ │ │ │ └── FeatureFlag__mdt-Feature Flag Layout.layout-meta.xml │ │ │ └── objects │ │ │ │ └── FeatureFlag__mdt │ │ │ │ ├── FeatureFlag__mdt.object-meta.xml │ │ │ │ └── fields │ │ │ │ └── Enabled__c.field-meta.xml │ │ │ └── test │ │ │ └── classes │ │ │ ├── AlwaysDisabledFeatureTest.cls │ │ │ ├── AlwaysDisabledFeatureTest.cls-meta.xml │ │ │ ├── AlwaysEnabledFeatureTest.cls │ │ │ ├── AlwaysEnabledFeatureTest.cls-meta.xml │ │ │ ├── CustomMetadataEnabledFeatureTest.cls │ │ │ ├── CustomMetadataEnabledFeatureTest.cls-meta.xml │ │ │ ├── CustomPermissionEnabledFeatureTest.cls │ │ │ └── CustomPermissionEnabledFeatureTest.cls-meta.xml │ └── tsconfig.json ├── core │ ├── .editorconfig │ ├── CHANGELOG.md │ ├── babel.config.js │ ├── jest.config.js │ ├── package.json │ ├── resources │ │ ├── metadatainfo.json │ │ └── pooldefinition.schema.json │ ├── src │ │ ├── apex │ │ │ ├── ApexClassFetcher.ts │ │ │ ├── ApexTriggerFetcher.ts │ │ │ ├── coverage │ │ │ │ ├── ApexCodeCoverageAggregateFetcher.ts │ │ │ │ └── IndividualClassCoverage.ts │ │ │ └── parser │ │ │ │ ├── ApexTypeFetcher.ts │ │ │ │ └── listeners │ │ │ │ └── ApexTypeListener.ts │ │ ├── apextest │ │ │ ├── ApexTestSuite.ts │ │ │ ├── ClearCodeCoverage.ts │ │ │ ├── ImpactedApexTestClassFetcher.ts │ │ │ ├── JSONReporter.ts │ │ │ ├── TestOptions.ts │ │ │ ├── TestReportDisplayer.ts │ │ │ └── TriggerApexTests.ts │ │ ├── artifacts │ │ │ ├── ArtifactFetcher.ts │ │ │ └── generators │ │ │ │ └── ArtifactGenerator.ts │ │ ├── changelog │ │ │ ├── GeneratePackageChangelog.ts │ │ │ └── interfaces │ │ │ │ └── GenericChangelogInterfaces.ts │ │ ├── dependency │ │ │ ├── ChangedComponentsFetcher.ts │ │ │ ├── Component.ts │ │ │ ├── DependencyViolation.ts │ │ │ └── Entrypoint.ts │ │ ├── deployers │ │ │ ├── DeploySourceToOrgImpl.ts │ │ │ ├── DeploymentExecutor.ts │ │ │ └── DeploymentSettingsService.ts │ │ ├── display │ │ │ ├── DependencyViolationDisplayer.ts │ │ │ ├── DeployErrorDisplayer.ts │ │ │ ├── DeploymentOptionDisplayer.ts │ │ │ ├── ExternalDependencyDisplayer.ts │ │ │ ├── InstalledArtifactsDisplayer.ts │ │ │ ├── InstalledPackagesDisplayer.ts │ │ │ ├── PackageComponentPrinter.ts │ │ │ ├── PackageDependencyDisplayer.ts │ │ │ ├── PackageMetadataPrinter.ts │ │ │ ├── PushErrorDisplayer.ts │ │ │ └── TableConstants.ts │ │ ├── git │ │ │ ├── Git.ts │ │ │ ├── GitDiffUtil.ts │ │ │ ├── GitIdentity.ts │ │ │ └── GitTags.ts │ │ ├── ignore │ │ │ └── IgnoreFiles.ts │ │ ├── index.ts │ │ ├── limits │ │ │ └── LimitsFetcher.ts │ │ ├── metadata │ │ │ ├── CustomFieldFetcher.ts │ │ │ ├── MetadataFetcher.ts │ │ │ ├── MetadataFiles.ts │ │ │ ├── MetadataInfo.ts │ │ │ └── SettingsFetcher.ts │ │ ├── org │ │ │ ├── OrgDetailsFetcher.ts │ │ │ ├── OrganizationFetcher.ts │ │ │ ├── SFPOrg.ts │ │ │ ├── ScratchOrgInfoFetcher.ts │ │ │ └── packageQuery │ │ │ │ └── InstalledPackagesQueryExecutor.ts │ │ ├── package │ │ │ ├── Package2Detail.ts │ │ │ ├── SfpPackage.ts │ │ │ ├── SfpPackageBuilder.ts │ │ │ ├── SfpPackageInquirer.ts │ │ │ ├── SfpPackageInstaller.ts │ │ │ ├── analyser │ │ │ │ ├── AnalyzerRegistry.ts │ │ │ │ ├── FHTAnalyzer.ts │ │ │ │ ├── FTAnalyzer.ts │ │ │ │ ├── PackageAnalyzer.ts │ │ │ │ └── PicklistAnalyzer.ts │ │ │ ├── components │ │ │ │ ├── DeployDestructiveManifestToOrgImpl.ts │ │ │ │ ├── MetadataCount.ts │ │ │ │ ├── PackageManifest.ts │ │ │ │ ├── PackageToComponent.ts │ │ │ │ └── ReconcileProfileAgainstOrgImpl.ts │ │ │ ├── coverage │ │ │ │ ├── PackageTestCoverage.ts │ │ │ │ └── PackageVersionCoverage.ts │ │ │ ├── dependencies │ │ │ │ ├── ExternalPackage2DependencyResolver.ts │ │ │ │ ├── PackageDependencyResolver.ts │ │ │ │ └── TransitiveDependencyResolver.ts │ │ │ ├── deploymentCustomizers │ │ │ │ ├── DeploymentCustomizer.ts │ │ │ │ ├── FHTEnabler.ts │ │ │ │ ├── FTEnabler.ts │ │ │ │ ├── MetadataDeploymentCustomizer.ts │ │ │ │ ├── PicklistEnabler.ts │ │ │ │ ├── PostDeployersRegistry.ts │ │ │ │ └── PreDeployersRegistry.ts │ │ │ ├── deploymentFilters │ │ │ │ ├── DeploymentFilter.ts │ │ │ │ ├── DeploymentFilterRegistry.ts │ │ │ │ └── EntitlementVersionFilter.ts │ │ │ ├── diff │ │ │ │ ├── PackageComponentDiff.ts │ │ │ │ └── PackageDiffImpl.ts │ │ │ ├── generators │ │ │ │ └── SfpPackageContentGenerator.ts │ │ │ ├── packageCreators │ │ │ │ ├── CreateDataPackageImpl.ts │ │ │ │ ├── CreateDiffPackageImpl.ts │ │ │ │ ├── CreatePackage.ts │ │ │ │ ├── CreateSourcePackageImpl.ts │ │ │ │ └── CreateUnlockedPackageImpl.ts │ │ │ ├── packageFormatConvertors │ │ │ │ └── SourceToMDAPIConvertor.ts │ │ │ ├── packageInstallers │ │ │ │ ├── InstallDataPackageImpl.ts │ │ │ │ ├── InstallPackage.ts │ │ │ │ ├── InstallSourcePackageImpl.ts │ │ │ │ ├── InstallUnlockedPackage.ts │ │ │ │ ├── InstallUnlockedPackageCollection.ts │ │ │ │ ├── InstallUnlockedPackageImpl.ts │ │ │ │ └── PackageInstallationResult.ts │ │ │ ├── packageMerger │ │ │ │ └── PackageMergeManager.ts │ │ │ ├── promote │ │ │ │ └── PromoteUnlockedPackageImpl.ts │ │ │ ├── propertyFetchers │ │ │ │ ├── AssignPermissionSetFetcher.ts │ │ │ │ ├── DestructiveManifestPathFetcher.ts │ │ │ │ ├── PropertyFetcher.ts │ │ │ │ └── ReconcileProfilePropertyFetcher.ts │ │ │ ├── validators │ │ │ │ └── PackageEmptyChecker.ts │ │ │ └── version │ │ │ │ ├── Package2VersionFetcher.ts │ │ │ │ ├── Package2VersionInstaller.ts │ │ │ │ ├── PackageVersionLister.ts │ │ │ │ └── PackageVersionUpdater.ts │ │ ├── permsets │ │ │ ├── AssignPermissionSets.ts │ │ │ ├── AssignPermissionSetsImpl.ts │ │ │ ├── PermissionSetFetcher.ts │ │ │ └── PermissionSetGroupUpdateAwaiter.ts │ │ ├── project │ │ │ ├── ProjectConfig.ts │ │ │ └── UserDefinedExternalDependency.ts │ │ ├── queryHelper │ │ │ ├── ChunkCollection.ts │ │ │ └── QueryHelper.ts │ │ ├── scratchorg │ │ │ ├── PasswordGenerator.ts │ │ │ ├── ScratchOrg.ts │ │ │ ├── ScratchOrgOperator.ts │ │ │ └── pool │ │ │ │ ├── ClientSourceTracking.ts │ │ │ │ ├── OrphanedOrgsDeleteImpl.ts │ │ │ │ ├── PoolBaseImpl.ts │ │ │ │ ├── PoolConfig.ts │ │ │ │ ├── PoolCreateImpl.ts │ │ │ │ ├── PoolDeleteImpl.ts │ │ │ │ ├── PoolError.ts │ │ │ │ ├── PoolFetchImpl.ts │ │ │ │ ├── PoolJobExecutor.ts │ │ │ │ ├── PoolListImpl.ts │ │ │ │ ├── PoolOrgDeleteImpl.ts │ │ │ │ ├── prequisitecheck │ │ │ │ ├── IsValidSfdxAuthUrl.ts │ │ │ │ └── PreRequisiteCheck.ts │ │ │ │ └── services │ │ │ │ ├── fetchers │ │ │ │ ├── GetUserEmail.ts │ │ │ │ ├── ScratchOrgInfoFetcher.ts │ │ │ │ └── ScratchOrgLimitsFetcher.ts │ │ │ │ └── updaters │ │ │ │ └── ScratchOrgInfoAssigner.ts │ │ ├── scriptExecutor │ │ │ └── ScriptExecutorHelpers.ts │ │ ├── sfdmuwrapper │ │ │ └── SFDMURunImpl.ts │ │ ├── stats │ │ │ ├── NativeMetricSender.ts │ │ │ ├── SFPStatsSender.ts │ │ │ └── nativeMetricSenderImpl │ │ │ │ ├── DataDogMetricSender.ts │ │ │ │ ├── NewRelicMetricSender.ts │ │ │ │ └── SplunkMetricSender.ts │ │ ├── utils │ │ │ ├── AliasList.ts │ │ │ ├── ChunkArray.ts │ │ │ ├── DefaultShell.ts │ │ │ ├── Delay.ts │ │ │ ├── FileSystem.ts │ │ │ ├── Fileutils.ts │ │ │ ├── GetFormattedTime.ts │ │ │ ├── ObjectCRUDHelper.ts │ │ │ ├── OnExit.ts │ │ │ ├── RandomId.ts │ │ │ ├── VersionNumberConverter.ts │ │ │ ├── extractDomainFromUrl.ts │ │ │ └── xml2json.ts │ │ └── vlocitywrapper │ │ │ ├── VlocityInitialInstall.ts │ │ │ ├── VlocityPackDeployImpl.ts │ │ │ ├── VlocityPackUpdateSettings.ts │ │ │ └── VlocityRefreshBase.ts │ ├── tests │ │ ├── apextest │ │ │ └── ApexTestSuite.test.ts │ │ ├── artifacts │ │ │ └── ArtifactsFromFileSystem.test.ts │ │ ├── coverage │ │ │ └── IndividualClassCoverage.test.ts │ │ ├── git │ │ │ └── GitTags.test.ts │ │ ├── org │ │ │ ├── ArtifactsToOrg.test.ts │ │ │ └── ListAllPackages.test.ts │ │ ├── package │ │ │ ├── Package2VersionFetcher.test.ts │ │ │ ├── PackageDiffImpl.test.ts │ │ │ ├── PackageManifest.test.ts │ │ │ ├── SFPackageBuilder.test.ts │ │ │ ├── analysers │ │ │ │ ├── FHTAnalyzer.test.ts │ │ │ │ └── FTAnalyzer.test.ts │ │ │ ├── coverage │ │ │ │ └── PackageTestCoverage.test.ts │ │ │ ├── dependencies │ │ │ │ ├── PackageDependencyResolver.test.ts │ │ │ │ └── TransitiveDependencyResolver.test.ts │ │ │ ├── deploymentFilters │ │ │ │ └── EntitlementVersionFilter.test.ts │ │ │ ├── packageMerger │ │ │ │ ├── PackageMergeManager.test.ts │ │ │ │ └── artifacts1 │ │ │ │ │ ├── core2_sfpowerscripts_artifact_1.0.4-1.zip │ │ │ │ │ └── feature-mgmt3_sfpowerscripts_artifact_1.0.6-1.zip │ │ │ └── propertyFetchers │ │ │ │ ├── AssignPermissionSetFetcher.test.ts │ │ │ │ ├── DestructiveManifestPathFetcher.test.ts │ │ │ │ └── ReconcileProfilePropertyFetcher.test.ts │ │ ├── permsets │ │ │ ├── AssignPermissionSets.test.ts │ │ │ ├── PermissionSetFetcher.test.ts │ │ │ └── PermissionSetGroupUpdateAwaiter.test.ts │ │ ├── project │ │ │ └── ProjectConfig.test.ts │ │ ├── queryHelper │ │ │ └── ChunkCollection.test.ts │ │ └── utils │ │ │ ├── ChunkArray.test.ts │ │ │ ├── FileSystem.test.ts │ │ │ ├── extractDomainFromUrl.test.ts │ │ │ └── resources │ │ │ └── a │ │ │ └── b │ │ │ ├── b1.file │ │ │ ├── c │ │ │ ├── c1.file │ │ │ └── c2.file │ │ │ ├── d │ │ │ ├── d1.file │ │ │ └── x │ │ │ │ └── x1.file │ │ │ └── e │ │ │ └── e1.file │ └── tsconfig.json ├── forcemula │ ├── LICENSE │ ├── README.md │ ├── img │ │ └── logo.png │ ├── lib │ │ ├── MetadataTypes.js │ │ ├── mappings │ │ │ └── cpq.js │ │ ├── parseTypes.js │ │ ├── parser │ │ │ ├── grammar.js │ │ │ ├── grammarChecks.js │ │ │ └── transformations.js │ │ └── utils.js │ ├── package.json │ ├── src │ │ └── index.js │ └── tests │ │ ├── e2e.test.js │ │ ├── grammarChecks.test.js │ │ ├── parseFields.test.js │ │ ├── transformations.test.js │ │ └── utils.test.js ├── sfdx-process-wrapper │ ├── README.md │ ├── package.json │ ├── src │ │ ├── SFDXCommand.ts │ │ ├── commandExecutor │ │ │ ├── DefaultProcessOptions.ts │ │ │ ├── ExecuteCommand.ts │ │ │ └── SpawnCommand.ts │ │ └── index.ts │ └── tsconfig.json ├── sfplogger │ ├── package.json │ ├── src │ │ └── SFPLogger.ts │ └── tsconfig.json ├── sfpowerscripts-cli │ ├── .babel.config.js │ ├── .snyk │ ├── CHANGELOG.md │ ├── README.md │ ├── bin │ │ ├── run │ │ └── run.cmd │ ├── jest.config.js │ ├── messages │ │ ├── analyze.json │ │ ├── analyze_with_PMD.json │ │ ├── artifacts_query.json │ │ ├── build.json │ │ ├── core-messages.md │ │ ├── create-package.json │ │ ├── create_data_package.json │ │ ├── create_delta_package.json │ │ ├── create_source_package.json │ │ ├── create_unlocked_package.json │ │ ├── dependency_expand.json │ │ ├── dependency_install.json │ │ ├── dependency_shrink.json │ │ ├── deploy.json │ │ ├── fetch.json │ │ ├── generate_changelog.json │ │ ├── impact_package.json │ │ ├── impact_release_config.json │ │ ├── increment_build_number.json │ │ ├── install_data_package.json │ │ ├── install_package.json │ │ ├── install_package_command.json │ │ ├── install_source_package.json │ │ ├── install_unlocked_package.json │ │ ├── metrics_report.json │ │ ├── org_profile_diff.json │ │ ├── patch.json │ │ ├── pool_delete.json │ │ ├── prepare.json │ │ ├── profile_merge.json │ │ ├── profile_reconcile.json │ │ ├── profile_retrieve.json │ │ ├── promote.json │ │ ├── publish.json │ │ ├── quickbuild.json │ │ ├── release.json │ │ ├── releasedefinition_generate.json │ │ ├── scratchorg_poolFetch.json │ │ ├── scratchorg_pool_metrics_publish.json │ │ ├── scratchorg_pool_org_delete.json │ │ ├── scratchorg_poollist.json │ │ ├── trigger_apex_test.json │ │ ├── validate.json │ │ ├── validateAgainstOrg.json │ │ └── validate_apex_coverage.json │ ├── package.json │ ├── resources │ │ └── schemas │ │ │ ├── releasedefinition.schema.json │ │ │ ├── releasedefinitiongenerator.schema.json │ │ │ └── sfdx-project.schema.json │ ├── src │ │ ├── BuildBase.ts │ │ ├── InstallPackageCommand.ts │ │ ├── PackageCreateCommand.ts │ │ ├── ProjectValidation.ts │ │ ├── SfpowerscriptsCommand.ts │ │ ├── commands │ │ │ ├── apextests │ │ │ │ └── trigger.ts │ │ │ ├── artifacts │ │ │ │ ├── fetch.ts │ │ │ │ └── query.ts │ │ │ ├── changelog │ │ │ │ └── generate.ts │ │ │ ├── dependency │ │ │ │ ├── expand.ts │ │ │ │ ├── install.ts │ │ │ │ └── shrink.ts │ │ │ ├── impact │ │ │ │ ├── package.ts │ │ │ │ └── releaseconfig.ts │ │ │ ├── metrics │ │ │ │ └── report.ts │ │ │ ├── orchestrator │ │ │ │ ├── build.ts │ │ │ │ ├── deploy.ts │ │ │ │ ├── prepare.ts │ │ │ │ ├── promote.ts │ │ │ │ ├── publish.ts │ │ │ │ ├── quickbuild.ts │ │ │ │ ├── release.ts │ │ │ │ ├── validate.ts │ │ │ │ └── validateAgainstOrg.ts │ │ │ ├── package │ │ │ │ ├── data │ │ │ │ │ ├── create.ts │ │ │ │ │ └── install.ts │ │ │ │ ├── install.ts │ │ │ │ ├── source │ │ │ │ │ ├── create.ts │ │ │ │ │ └── install.ts │ │ │ │ └── unlocked │ │ │ │ │ ├── create.ts │ │ │ │ │ └── install.ts │ │ │ ├── pool │ │ │ │ ├── delete.ts │ │ │ │ ├── fetch.ts │ │ │ │ ├── list.ts │ │ │ │ ├── metrics │ │ │ │ │ └── publish.ts │ │ │ │ └── org │ │ │ │ │ └── delete.ts │ │ │ ├── profile │ │ │ │ ├── merge.ts │ │ │ │ ├── reconcile.ts │ │ │ │ └── retrieve.ts │ │ │ ├── releasedefinition │ │ │ │ └── generate.ts │ │ │ └── repo │ │ │ │ └── patch.ts │ │ ├── errors │ │ │ ├── ReleaseError.ts │ │ │ ├── SfpowerscriptsError.ts │ │ │ └── ValidateError.ts │ │ ├── flags │ │ │ ├── duration.ts │ │ │ ├── orgApiVersion.ts │ │ │ ├── salesforceId.ts │ │ │ └── sfdxflags.ts │ │ ├── impl │ │ │ ├── Stage.ts │ │ │ ├── artifacts │ │ │ │ ├── FetchAnArtifact.ts │ │ │ │ ├── FetchAnArtifactFromNPM.ts │ │ │ │ ├── FetchAnArtifactUsingScript.ts │ │ │ │ ├── FetchArtifactSelector.ts │ │ │ │ ├── FetchArtifactsError.ts │ │ │ │ └── FetchImpl.ts │ │ │ ├── changelog │ │ │ │ ├── ChangelogImpl.ts │ │ │ │ ├── ChangelogMarkdownGenerator.ts │ │ │ │ ├── CommitUpdater.ts │ │ │ │ ├── OrgsUpdater.ts │ │ │ │ ├── ReadPackageChangelog.ts │ │ │ │ ├── ReleaseChangelog.ts │ │ │ │ ├── ReleaseChangelogUpdater.ts │ │ │ │ └── WorkItemUpdater.ts │ │ │ ├── demoreelplayer │ │ │ │ └── DemoReelPlayer.ts │ │ │ ├── dependency │ │ │ │ └── ShrinkImpl.ts │ │ │ ├── deploy │ │ │ │ ├── DeployImpl.ts │ │ │ │ ├── PostDeployHook.ts │ │ │ │ └── PreDeployHook.ts │ │ │ ├── impact │ │ │ │ ├── ImpactedPackagesResolver.ts │ │ │ │ └── ImpactedReleaseConfig.ts │ │ │ ├── parallelBuilder │ │ │ │ ├── BatchingTopoSort.ts │ │ │ │ ├── BuildCollections.ts │ │ │ │ ├── BuildImpl.ts │ │ │ │ ├── DependencyHelper.ts │ │ │ │ └── UndirectedGraph.ts │ │ │ ├── prepare │ │ │ │ ├── PrepareImpl.ts │ │ │ │ └── PrepareOrgJob.ts │ │ │ ├── release │ │ │ │ ├── ReleaseConfig.ts │ │ │ │ ├── ReleaseDefinition.ts │ │ │ │ ├── ReleaseDefinitionGenerator.ts │ │ │ │ ├── ReleaseDefinitionGeneratorConfigSchema.ts │ │ │ │ ├── ReleaseDefinitionSchema.ts │ │ │ │ └── ReleaseImpl.ts │ │ │ ├── repo │ │ │ │ └── AlignImpl.ts │ │ │ └── validate │ │ │ │ ├── Analyzer.ts │ │ │ │ ├── ApexTestValidator.ts │ │ │ │ ├── ValidateImpl.ts │ │ │ │ └── ValidateResult.ts │ │ ├── index.ts │ │ ├── outputs │ │ │ └── FileOutputHandler.ts │ │ ├── ui │ │ │ ├── GroupConsoleLogs.ts │ │ │ ├── OrgInfoDisplayer.ts │ │ │ └── TableConstants.ts │ │ └── utils │ │ │ ├── FetchArtifactsFromOrg.ts │ │ │ └── Get18DigitSalesforceId.ts │ ├── tests │ │ ├── ProjectValidation.test.ts │ │ └── impl │ │ │ ├── changelog │ │ │ ├── CommitUpdater.test.ts │ │ │ ├── OrgUpdater.test.ts │ │ │ ├── WorkItemUpdater.test.ts │ │ │ └── resources │ │ │ │ ├── ESBaseCodeLWCChangelog.json │ │ │ │ ├── ESBaseStylesLWCChangelog.json │ │ │ │ ├── ESObjectsChangelog.json │ │ │ │ ├── ESSpaceMgmtLWCChangelog.json │ │ │ │ └── ExpectedResults │ │ │ │ ├── should_update_latestRelease_with_all_commits.json │ │ │ │ ├── should_update_latestRelease_with_subset_of_commits.json │ │ │ │ └── should_update_latestRelease_with_work_items.json │ │ │ ├── dependency │ │ │ └── ShrinkImpl.test.ts │ │ │ ├── parallelBuilder │ │ │ ├── BuildCollections.test.ts │ │ │ └── UndirectedGraph.test.ts │ │ │ └── release │ │ │ └── ReleaseDefinition.test.ts │ └── tsconfig.json └── sfprofiles │ ├── .editorconfig │ ├── .eslintrc.js │ ├── babel.config.js │ ├── jest.config.js │ ├── package.json │ ├── resources │ └── metadatainfo.json │ ├── src │ ├── impl │ │ ├── diff │ │ │ ├── customLabelsDiff.ts │ │ │ ├── diffImpl.ts │ │ │ ├── diffUtil.ts │ │ │ ├── permsetDiff.ts │ │ │ ├── profileDiff.ts │ │ │ ├── sharingRuleDiff.ts │ │ │ └── workflowDiff.ts │ │ ├── metadata │ │ │ ├── builder │ │ │ │ └── userPermissionBuilder.ts │ │ │ ├── metadataFiles.ts │ │ │ ├── metadataInfo.ts │ │ │ ├── packageBuilder.ts │ │ │ ├── retriever │ │ │ │ ├── metadataRetriever.ts │ │ │ │ ├── metadataSummaryInfoFetcher.ts │ │ │ │ └── profileRetriever.ts │ │ │ ├── schema.ts │ │ │ └── writer │ │ │ │ └── profileWriter.ts │ │ ├── source │ │ │ ├── profileActions.ts │ │ │ ├── profileComponentReconciler.ts │ │ │ ├── profileDiff.ts │ │ │ ├── profileMerge.ts │ │ │ ├── profileReconcile.ts │ │ │ ├── profileSync.ts │ │ │ ├── reconcileWorker.ts │ │ │ └── worker.js │ │ └── user │ │ │ └── passwordgenerateimpl.ts │ ├── sfprofiles.js │ └── utils │ │ ├── checkDeploymentStatus.ts │ │ ├── checkRetrievalStatus.ts │ │ ├── chunkArray.ts │ │ ├── delay.ts │ │ ├── dxProjectManifestUtils.ts │ │ ├── extract.ts │ │ ├── fileutils.ts │ │ ├── getDefaults.ts │ │ ├── getPackageInfo.ts │ │ ├── metadataOperation.ts │ │ ├── packageUtils.ts │ │ ├── queryExecutor.ts │ │ ├── retrieveMetadata.ts │ │ ├── searchFilesInDirectory.ts │ │ ├── sfpowerkit.ts │ │ ├── sqlitekv.ts │ │ ├── xmlUtil.ts │ │ └── zipDirectory.ts │ ├── tests │ └── sfprofiles.test.js │ └── tsconfig.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── reviewpad.yml └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: azlam-abdulsalam 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: 'Create a report to help us fix the issue ' 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Platform Details (please complete the following information):** 24 | - OS: 25 | - Version [e.g. CLI Version eg: 1.6.6] 26 | - Salesforce CLI(sfdx cli) Version: 27 | - CI Platform: 28 | 29 | 30 | **Additional context** 31 | Add any other context about the problem here. 32 | -------------------------------------------------------------------------------- /.github/actions/copyDocker/action.yml: -------------------------------------------------------------------------------- 1 | name: Copy Docker Image 2 | description: ' Docker Image across the same repo as a different image' 3 | 4 | inputs: 5 | repo: 6 | type: string 7 | required: true 8 | image: 9 | type: string 10 | required: true 11 | image-as: 12 | type: string 13 | required: true 14 | tag: 15 | type: string 16 | required: true 17 | with-tag: 18 | type: string 19 | required: true 20 | registry: 21 | type: string 22 | required: false 23 | default: ghcr.io 24 | username: 25 | type: string 26 | required: true 27 | token: 28 | type: string 29 | required: true 30 | 31 | 32 | runs: 33 | using: "composite" 34 | steps: 35 | 36 | - uses: actions/setup-go@v2 37 | with: 38 | go-version: 1.15 39 | 40 | - uses: imjasonh/setup-crane@v0.1 41 | 42 | - name: tag image 43 | shell: bash 44 | run: | 45 | echo "${{ inputs.token }}" | crane auth login ${{ inputs.registry }} --username ${{ inputs.username }} --password-stdin 46 | crane cp ${{ inputs.registry }}/${{ inputs.repo }}/${{ inputs.image }}:${{ inputs.tag }} ${{ inputs.registry }}/${{ inputs.repo}}/${{ inputs.image-as }}:${{ inputs.with-tag }} 47 | 48 | 49 | -------------------------------------------------------------------------------- /.github/actions/tagDocker/action.yml: -------------------------------------------------------------------------------- 1 | name: Tag Docker Image in ghcr 2 | description: 'Tag an existing image in ghcr' 3 | 4 | inputs: 5 | repo: 6 | type: string 7 | required: true 8 | image: 9 | type: string 10 | required: true 11 | existing-tag: 12 | type: string 13 | required: true 14 | new-tag: 15 | type: string 16 | required: true 17 | registry: 18 | type: string 19 | required: false 20 | default: ghcr.io 21 | username: 22 | type: string 23 | required: true 24 | token: 25 | type: string 26 | required: true 27 | 28 | 29 | runs: 30 | using: "composite" 31 | steps: 32 | 33 | - uses: actions/setup-go@v2 34 | with: 35 | go-version: 1.15 36 | 37 | - uses: imjasonh/setup-crane@v0.1 38 | 39 | - name: tag image 40 | shell: bash 41 | run: | 42 | echo "${{ inputs.token }}" | crane auth login ${{ inputs.registry }} --username ${{ inputs.username }} --password-stdin 43 | crane tag ${{ inputs.registry }}/${{ inputs.repo }}/${{ inputs.image }}:${{ inputs.existing-tag }} ${{ inputs.new-tag }} 44 | 45 | 46 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | reviewpad:summary 3 | 4 | 5 | 6 | #### Checklist 7 | All items have to be completed before a PR is merged 8 | 9 | - [x] Adhere to [Contribution Guidelines](https://docs.dxatscale.io/about-us/contributing-to-dx-scale) 10 | - [ ] Updates to Decision Records considered? 11 | - [ ] Updates to documentation at [DX@Scale Guide](https://github.com/dxatscale/dxatscale-guide) considered? 12 | - [ ] Tested changes? 13 | - [ ] Unit Tests new and existing passing locally? 14 | 15 | -------------------------------------------------------------------------------- /.github/workflows/cherry-picker.yml: -------------------------------------------------------------------------------- 1 | name: PR for release branch 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | release_pull_request: 8 | runs-on: ubuntu-latest 9 | name: release_pull_request 10 | steps: 11 | - name: checkout 12 | uses: actions/checkout@v1 13 | - name: Create PR to branch 14 | uses: gorillio/github-action-cherry-pick@master 15 | with: 16 | pr_branch: 'develop' 17 | pr_labels: 'autocreated, main' 18 | env: 19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 20 | GITBOT_EMAIL: dxatscale@accenture.com 21 | DRY_RUN: false 22 | -------------------------------------------------------------------------------- /.github/workflows/issue-label-analysis.yml: -------------------------------------------------------------------------------- 1 | name: issue-automation-initial-labeller 2 | 3 | on: 4 | issues: 5 | types: [opened] 6 | 7 | jobs: 8 | automate-issues-labels: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: apply initial label 12 | uses: andymckay/labeler@master 13 | with: 14 | add-labels: "analysis" 15 | -------------------------------------------------------------------------------- /.github/workflows/pull-request-task-checker.yml: -------------------------------------------------------------------------------- 1 | name: 'PR Tasks Completed Check' 2 | on: 3 | pull_request: 4 | types: [opened, edited, reopened, synchronize] 5 | branches: 6 | - develop 7 | 8 | jobs: 9 | task-check: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: kentaro-m/task-completed-checker-action@v0.1.0 13 | with: 14 | repo-token: "${{ secrets.GITHUB_TOKEN }}" 15 | -------------------------------------------------------------------------------- /.github/workflows/release-notes-generator.yml: -------------------------------------------------------------------------------- 1 | # Trigger the workflow on milestone events 2 | on: 3 | milestone: 4 | types: [closed] 5 | workflow_dispatch: 6 | inputs: 7 | milestoneId: 8 | description: 'Milestone ID' 9 | required: true 10 | default: '1' 11 | name: Generate Release Notes 12 | jobs: 13 | create-release-notes: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@master 17 | - name: Create Release Notes 18 | uses: docker://decathlon/release-notes-generator-action:3.1.4 19 | env: 20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 21 | OUTPUT_FOLDER: release_notes 22 | USE_MILESTONE_TITLE: "true" 23 | -------------------------------------------------------------------------------- /.github/workflows/review.yml: -------------------------------------------------------------------------------- 1 | # Unique name for this workflow 2 | name: Validate PR and Run Tests 3 | 4 | # Definition when the workflow should run 5 | on: 6 | workflow_dispatch: 7 | pull_request: 8 | types: [opened, synchronize, reopened] 9 | branches: 10 | - develop 11 | - main 12 | 13 | # Jobs to be executed 14 | jobs: 15 | validate: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Use Node.js 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: '16.x' 23 | registry-url: 'https://registry.npmjs.org' 24 | - uses: pnpm/action-setup@v2 25 | with: 26 | version: 8 27 | 28 | - name: 'Install Dependencies' 29 | run: pnpm i 30 | - run: npx lerna run build 31 | - run: npx lerna run test -- --colors 32 | - run: bash <(curl -s https://codecov.io/bash) -v 33 | 34 | 35 | -------------------------------------------------------------------------------- /.github/workflows/tagDockerImage.yml: -------------------------------------------------------------------------------- 1 | name: Promote Docker Image in ghcr manually 2 | 3 | 4 | on: 5 | workflow_dispatch: 6 | inputs: 7 | repo: 8 | type: string 9 | required: true 10 | default: dxatscale 11 | image: 12 | type: string 13 | required: true 14 | default: sfpowerscripts 15 | existing-tag: 16 | type: string 17 | required: true 18 | default: latest 19 | new-tag: 20 | type: string 21 | required: true 22 | default: latest 23 | 24 | jobs: 25 | 26 | build: 27 | name: 'tag sfpowerscripts docker image' 28 | environment: tagger 29 | runs-on: ubuntu-latest 30 | steps: 31 | 32 | - uses: actions/checkout@v2 33 | with: 34 | fetch-depth: 0 35 | 36 | 37 | - name: 'Tag Docker' 38 | uses: ./.github/actions/tagDocker 39 | with: 40 | repo: ${{ github.event.inputs.repo }} 41 | image: ${{ github.event.inputs.image }} 42 | existing-tag: ${{ github.event.existing-tag }} 43 | new-tag: ${{ github.event.new-tag }} 44 | registry: ghcr.io 45 | username: ${{ secrets.DOCKER_USERNAME }} 46 | token: ${{ secrets.DOCKER_SECRET }} 47 | 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib 2 | !packages/forcemula/lib 3 | !packages/sfdc-soup/lib 4 | tsconfig.tsbuildinfo 5 | node_modules 6 | build 7 | dist 8 | packages/azpipelines/.taskkey 9 | .npmrc 10 | lerna-debug.log 11 | tmp/ 12 | oclif.manifest.json 13 | 14 | packages/azpipelines_release_management_extensions/BuildTasks/ManageReleaseTask/* 15 | 16 | packages/core/coverage 17 | packages/sfpowerscripts-cli/coverage 18 | .DS_Store 19 | packages/core/.sfpowerscripts/** 20 | 21 | coverage/** 22 | packages/apexlink/coverage 23 | packages/sfpowerscripts-cli/oclif.manifest.json 24 | packages/apexlink/tests/resources/core-crm/apexlink.json 25 | packages/apexlink/tests/resources/feature-mgmt/apexlink.json 26 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | lib 2 | tsconfig.tsbuildinfo 3 | node_modules 4 | build 5 | dist 6 | .npmrc 7 | lerna-debug.log 8 | tmp/ 9 | oclif.manifest.json 10 | packages/azpipelines_release_management_extensions/BuildTasks/ManageReleaseTask/* 11 | packages/core/coverage 12 | packages/sfpowerscripts-cli/coverage 13 | .DS_Store 14 | packages/core/resources 15 | packages/sfp-cli/resources 16 | packages/sfpowerscripts-cli/resources 17 | .github 18 | decision records 19 | demoreel 20 | **/README.md 21 | Third Party Notices.md 22 | *.yml 23 | CONTRIBUTING.md 24 | prerequisites/scratchorgpool/** -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "trailingComma": "es5", 4 | "useTabs": false, 5 | "tabWidth": 4, 6 | "semi": true, 7 | "singleQuote": true, 8 | "bracketSpacing": true 9 | } 10 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.disableAutomaticTypeAcquisition": true, 3 | "files.exclude": { 4 | "**/.git": true, 5 | "**/.svn": true, 6 | "**/.hg": true, 7 | "**/CVS": true, 8 | "**/.DS_Store": true, 9 | "**/node_modules": true, 10 | "packages/forcemula/lib":false, 11 | "lib":true, 12 | "**/[^forcemula|^sfdc-soup]*/lib":true, 13 | }, 14 | "files.associations": { 15 | "*.log": "vt100" 16 | }, 17 | "editor.tabSize": 2, 18 | "conventionalCommits.scopes": [ 19 | "sfp-cli", 20 | "core", 21 | "sfpowerscripts" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributing to sfpowerscripts 3 | 4 | First and foremost, **thank you**! We appreciate that you want to contribute to sfpowerscripts, your time is valuable, and your contributions mean a lot to us. 5 | 6 | ## Important! 7 | 8 | By contributing to this project you agree that: 9 | 10 | * you have authored 100% of the content 11 | * you have the necessary rights to the content 12 | * you have received the necessary permissions from your employer to make the contributions \(if applicable\) 13 | * the content you contribute may be provided under the Project license\(s\) 14 | * if you did not author 100% of the content, the appropriate licenses and copyrights have been added along with any other necessary attribution. 15 | 16 | ## Getting started 17 | 18 | **What does “contributing” mean?** 19 | 20 | Creating an issue is the simplest form of contributing to the project. But there are other ways to contribute: 21 | 22 | * Updating or correcting documentation 23 | * Feature requests 24 | * Bug reports 25 | 26 | ## Issues 27 | 28 | Please only create issues for bug reports or feature requests at [sfpowerscripts repo](https://github.com/dxatscale/sfpowerscripts). Issues discussing any other topics may be closed by the project’s maintainers without further explanation. 29 | 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 dxatscale 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do 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. 22 | -------------------------------------------------------------------------------- /decision records/002-parallel-development-process.md: -------------------------------------------------------------------------------- 1 | # Process for creating parallel development streams 2 | 3 | * Status: Rejected 4 | 5 | 6 | ## Context and Problem Statement 7 | 8 | When creating a parallel development streams (e.g. release), there are a list of manual steps that must be performed: 9 | 10 | - package versions may need to be updated so that packages between streams do not share the same version-space 11 | - a new artifact feed or npm tags need to be created 12 | - a set of artifacts needs to be created for the new development stream 13 | 14 | ## Options 15 | 1. **CLI helper tool for creating parallel dev streams** 16 | - Create a CLI tool that automates the tasks required when creating parallel development streams: 17 | - fetch last set of artifacts from source feed and publish them to the target feed; or 18 | - create NPM tags that point to the latest artifact versions from parent stream 19 | - increments package versions in the parent stream 20 | - create a new git branch 21 | 2. **Document the process** 22 | - Document the process for creating parallel development streams in Gitbook 23 | 24 | ## Decision 25 | 26 | All this issues arised from the fact that with the assumption of using multiple feeds. As we are moving to ask users to utilize a single feed/artifact repository and how it is in most platforms like GitHub or GitLab, there is no specific need for a helper tool. Users should be versioning their artifacts using semantic version when dealing with multiple development streams 27 | -------------------------------------------------------------------------------- /decision records/005-package-dependency-version-resolution.md: -------------------------------------------------------------------------------- 1 | # Package dependency version resolution 2 | • Status: Accepted 3 | • Issue: #496 4 | 5 | ## Context and Problem Statement 6 | The Build command resolves package dependency versions, using `sfpowerkit:package:dependencies:list`, but there are several shortcomings with the current implementation: 7 | 8 | 1. Executing sfpowerkit CLI in a node process 9 | 1. The package versions are not filtered to the branch 10 | 1. Dependencies on packages that are part of the same build are not guaranteed to pick up the version created in the build; it just fetches the LATEST version 11 | 1. The resolved dependencies are not reflected in the artifact metadata or sfdx-project.json 12 | 13 | ## Solution 14 | 15 | To address the first issue, the sfpowerkit command will be replaced with a direct API call and implementation within sfpowerscripts. This includes a fetcher for the Package2Version sObject. 16 | At the start of a build, the package dependencies will be resolved in one go, leaving dependencies on packages that are part of the same build to be resolved dynamically, as they are created. 17 | For dependencies on packages that are part of the same repo, the validated package versions are filtered to the current branch using git tags created by Publish. 18 | 19 | ![image](https://user-images.githubusercontent.com/43767972/173757853-ee26195e-0a5c-4adb-9dac-826defb0b02d.png) 20 | 21 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": ["packages/*"], 3 | "npmClient":"pnpm", 4 | "version": "independent", 5 | "command": { 6 | "publish": { 7 | "yes": true, 8 | "ignoreChanges": ["*.md"], 9 | "registry": "https://registry.npmjs.org", 10 | "message": "chore(publish): update versions and publish to npm" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/nx/schemas/nx-schema.json", 3 | "tasksRunnerOptions": { 4 | "default": { 5 | "runner": "nx/tasks-runners/default", 6 | "options": { 7 | "cacheableOperations": ["build", "compile", "test"] 8 | } 9 | } 10 | }, 11 | "targetDefaults": { 12 | "build": { 13 | "dependsOn": ["^build"], 14 | "outputs": ["{projectRoot}/lib"] 15 | }, 16 | "compile": { 17 | "outputs": ["{projectRoot}/lib"] 18 | } 19 | }, 20 | "namedInputs": { 21 | "default": ["{projectRoot}/**/*", "sharedGlobals"], 22 | "sharedGlobals": [], 23 | "production": ["default"] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sfpowerscripts", 3 | "private": true, 4 | "license": "MIT", 5 | "scripts": { 6 | "format": "npx prettier --write .", 7 | "preinstall": "npx only-allow pnpm" 8 | }, 9 | "devDependencies": { 10 | "@commitlint/cli": "^15.0.0", 11 | "@commitlint/config-conventional": "^15.0.0", 12 | "@types/async-retry": "^1.4.2", 13 | "@types/fs-extra": "^9.0.11", 14 | "@types/mocha": "^5.2.7", 15 | "@types/node": "^10", 16 | "@types/q": "^1.5.2", 17 | "@types/xml2js": "^0.4.5", 18 | "lerna": "^7.1.4", 19 | "lerna-update-wizard": "^1.1.0", 20 | "prettier": "^2.0.5", 21 | "rimraf": "^3.0.2", 22 | "semver": "7.5.2", 23 | "ts-loader": "~9.4.2", 24 | "ts-node": "^9", 25 | "typescript": "^5" 26 | }, 27 | "dependencies": { 28 | "neverthrow": "^4.2.1", 29 | "xml-formatter": "^3.3.2" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/apexlink/README.md: -------------------------------------------------------------------------------- 1 | # @dxatscale/apexlink` 2 | 3 | ApexLink is the a thin node invoker for @nawforce/apex-link, which is delivered as a JAR file. This invoker executes a sub process where the apex link jar is executed 4 | 5 | -------------------------------------------------------------------------------- /packages/apexlink/jars/antlr4-runtime-4.8-1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dxatscale/sfpowerscripts/e1b48d7c9e599a085d5eabd70d382fbdd8dabb13/packages/apexlink/jars/antlr4-runtime-4.8-1.jar -------------------------------------------------------------------------------- /packages/apexlink/jars/apex-parser-3.0.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dxatscale/sfpowerscripts/e1b48d7c9e599a085d5eabd70d382fbdd8dabb13/packages/apexlink/jars/apex-parser-3.0.0.jar -------------------------------------------------------------------------------- /packages/apexlink/jars/apexlink-2.3.7.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dxatscale/sfpowerscripts/e1b48d7c9e599a085d5eabd70d382fbdd8dabb13/packages/apexlink/jars/apexlink-2.3.7.jar -------------------------------------------------------------------------------- /packages/apexlink/jars/geny_2.13-0.6.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dxatscale/sfpowerscripts/e1b48d7c9e599a085d5eabd70d382fbdd8dabb13/packages/apexlink/jars/geny_2.13-0.6.2.jar -------------------------------------------------------------------------------- /packages/apexlink/jars/pkgforce_2.13-2.3.7.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dxatscale/sfpowerscripts/e1b48d7c9e599a085d5eabd70d382fbdd8dabb13/packages/apexlink/jars/pkgforce_2.13-2.3.7.jar -------------------------------------------------------------------------------- /packages/apexlink/jars/runforce-55.5.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dxatscale/sfpowerscripts/e1b48d7c9e599a085d5eabd70d382fbdd8dabb13/packages/apexlink/jars/runforce-55.5.0.jar -------------------------------------------------------------------------------- /packages/apexlink/jars/scala-collection-compat_2.13-2.1.4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dxatscale/sfpowerscripts/e1b48d7c9e599a085d5eabd70d382fbdd8dabb13/packages/apexlink/jars/scala-collection-compat_2.13-2.1.4.jar -------------------------------------------------------------------------------- /packages/apexlink/jars/scala-json-rpc-upickle-json-serializer_2.13-1.0.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dxatscale/sfpowerscripts/e1b48d7c9e599a085d5eabd70d382fbdd8dabb13/packages/apexlink/jars/scala-json-rpc-upickle-json-serializer_2.13-1.0.1.jar -------------------------------------------------------------------------------- /packages/apexlink/jars/scala-json-rpc_2.13-1.0.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dxatscale/sfpowerscripts/e1b48d7c9e599a085d5eabd70d382fbdd8dabb13/packages/apexlink/jars/scala-json-rpc_2.13-1.0.1.jar -------------------------------------------------------------------------------- /packages/apexlink/jars/scala-library-2.13.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dxatscale/sfpowerscripts/e1b48d7c9e599a085d5eabd70d382fbdd8dabb13/packages/apexlink/jars/scala-library-2.13.3.jar -------------------------------------------------------------------------------- /packages/apexlink/jars/scala-parallel-collections_2.13-1.0.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dxatscale/sfpowerscripts/e1b48d7c9e599a085d5eabd70d382fbdd8dabb13/packages/apexlink/jars/scala-parallel-collections_2.13-1.0.0.jar -------------------------------------------------------------------------------- /packages/apexlink/jars/scala-reflect-2.13.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dxatscale/sfpowerscripts/e1b48d7c9e599a085d5eabd70d382fbdd8dabb13/packages/apexlink/jars/scala-reflect-2.13.3.jar -------------------------------------------------------------------------------- /packages/apexlink/jars/scala-xml_2.13-1.3.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dxatscale/sfpowerscripts/e1b48d7c9e599a085d5eabd70d382fbdd8dabb13/packages/apexlink/jars/scala-xml_2.13-1.3.0.jar -------------------------------------------------------------------------------- /packages/apexlink/jars/ujson_2.13-1.2.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dxatscale/sfpowerscripts/e1b48d7c9e599a085d5eabd70d382fbdd8dabb13/packages/apexlink/jars/ujson_2.13-1.2.0.jar -------------------------------------------------------------------------------- /packages/apexlink/jars/upack_2.13-1.2.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dxatscale/sfpowerscripts/e1b48d7c9e599a085d5eabd70d382fbdd8dabb13/packages/apexlink/jars/upack_2.13-1.2.0.jar -------------------------------------------------------------------------------- /packages/apexlink/jars/upickle-core_2.13-1.2.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dxatscale/sfpowerscripts/e1b48d7c9e599a085d5eabd70d382fbdd8dabb13/packages/apexlink/jars/upickle-core_2.13-1.2.0.jar -------------------------------------------------------------------------------- /packages/apexlink/jars/upickle-implicits_2.13-1.2.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dxatscale/sfpowerscripts/e1b48d7c9e599a085d5eabd70d382fbdd8dabb13/packages/apexlink/jars/upickle-implicits_2.13-1.2.0.jar -------------------------------------------------------------------------------- /packages/apexlink/jars/upickle_2.13-1.2.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dxatscale/sfpowerscripts/e1b48d7c9e599a085d5eabd70d382fbdd8dabb13/packages/apexlink/jars/upickle_2.13-1.2.0.jar -------------------------------------------------------------------------------- /packages/apexlink/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest/presets/js-with-babel', 3 | testEnvironment: 'node', 4 | restoreMocks: true, 5 | clearMocks: true, 6 | resetMocks: true, 7 | transform: { 8 | '^.+\\.[t]sx?$': [ 9 | 'ts-jest', 10 | { 11 | tsconfig: 'tsconfig.json', 12 | babelConfig: true, 13 | }, 14 | ] 15 | }, 16 | transformIgnorePatterns: ['/node_modules/(?!@salesforce/source-deploy-retrieve)(.*)'], 17 | }; 18 | -------------------------------------------------------------------------------- /packages/apexlink/src/index.ts: -------------------------------------------------------------------------------- 1 | export default {}; 2 | -------------------------------------------------------------------------------- /packages/apexlink/tests/ApexDependencyCheckImpl.test.ts: -------------------------------------------------------------------------------- 1 | import { jest, expect } from '@jest/globals'; 2 | import { ConsoleLogger } from '@dxatscale/sfp-logger'; 3 | import ApexDepedencyCheckImpl from '../src/ApexDepedencyCheckImpl'; 4 | import path from 'path'; 5 | 6 | describe('Given a directory with apex classes, ', () => { 7 | it('it should provide the dependendencies of apex class', async () => { 8 | 9 | let apexLinkImpl = new ApexDepedencyCheckImpl(new ConsoleLogger(), path.join(__dirname,`/resources/feature-mgmt`)); 10 | let result = await apexLinkImpl.execute(); 11 | expect(result.dependencies).toContainEqual({ "name": "AlwaysEnabledFeature", "dependencies": ["Feature"]}); 12 | 13 | },30000); 14 | }); 15 | 16 | describe('Given a directory with no apex classes, ', () => { 17 | it('it should provide an empty array', async () => { 18 | 19 | let apexLinkImpl = new ApexDepedencyCheckImpl(new ConsoleLogger(), path.join(__dirname,`/resources/core-crm`)); 20 | let result = await apexLinkImpl.execute(); 21 | expect(result.dependencies.length).toEqual(0); 22 | 23 | },50000); 24 | }); -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/core-crm/README.md: -------------------------------------------------------------------------------- 1 | A folder to house all the core model of your org which is shared with all other domains. -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/core-crm/main/objects/Account/fields/AccountNumber2__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | AccountNumber2__c 4 | Account number 5 | false 6 | The account number 2 7 | 8 | 225 9 | false 10 | true 11 | false 12 | Text 13 | false 14 | 15 | -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/core-crm/main/objects/Account/fields/AccountNumber3__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | AccountNumber3__c 4 | Account number 5 | false 6 | The account number 2 7 | 8 | 225 9 | false 10 | true 11 | false 12 | Text 13 | false 14 | 15 | -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/core-crm/main/objects/Account/fields/AccountNumber__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | AccountNumber__c 4 | Account number 5 | false 6 | The account number 7 | 8 | 235 9 | false 10 | false 11 | Text 12 | false 13 | 14 | -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/feature-mgmt/core/main/classes/CustomMetadataFeatureSettingsProvider.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * @description Feature Settings Provider that returns Feature Settings 3 | * from the FeatureSetting__mdt Custom Metadata 4 | */ 5 | public class CustomMetadataFeatureSettingsProvider implements FeatureSettingsProvider { 6 | /** 7 | * @description Returns Feature Setting Object by Feature Name 8 | * if not found should return null 9 | * @param featureName 10 | * @return Feature Setting Object null if not found 11 | */ 12 | public FeatureSetting getFeatureSettingByName(String featureName) { 13 | FeatureSetting__mdt featureSettingMetadata = FeatureSetting__mdt.getInstance( 14 | featureName 15 | ); 16 | 17 | if (featureSettingMetadata == null) { 18 | return null; 19 | } 20 | 21 | return new FeatureSetting( 22 | featureName, 23 | featureSettingMetadata?.Implementation__c 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/feature-mgmt/core/main/classes/CustomMetadataFeatureSettingsProvider.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 52.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/feature-mgmt/core/main/classes/Feature.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * @description Base Interface for all feature flags 3 | */ 4 | public interface Feature { 5 | /** 6 | * @description Returns true if feature is enabled 7 | * 8 | * @param featureName name of the feature to be checked 9 | * @return true if enabled 10 | */ 11 | Boolean isEnabled(String featureName); 12 | } 13 | -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/feature-mgmt/core/main/classes/Feature.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 52.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/feature-mgmt/core/main/classes/FeatureChecker.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * @description Feature Checker Interface 3 | * Responsible for checking if a feature is enabled or not 4 | */ 5 | public interface FeatureChecker { 6 | /** 7 | * @description Returns true if feature is enabled 8 | * @param featureName feature to be checked 9 | * @return true if enabled 10 | */ 11 | Boolean isEnabled(String featureName); 12 | } 13 | -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/feature-mgmt/core/main/classes/FeatureChecker.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 52.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/feature-mgmt/core/main/classes/FeatureCheckerImplementation.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * @description Default implementation of the Feature Checker 3 | * Uses a custom metadata table to fetch the a Feature implementation 4 | * to determin if the feature should be enabled 5 | */ 6 | public class FeatureCheckerImplementation implements FeatureChecker { 7 | private FeatureSettingsProvider featureSettingsProvider; 8 | 9 | /** 10 | * @description No Parameter Constructor 11 | * Sets the default FeatureSettings Provider 12 | */ 13 | public FeatureCheckerImplementation() { 14 | this(new CustomMetadataFeatureSettingsProvider()); 15 | } 16 | 17 | /** 18 | * @description Constructor 19 | * @param featureSettingsProvider 20 | */ 21 | public FeatureCheckerImplementation( 22 | FeatureSettingsProvider featureSettingsProvider 23 | ) { 24 | this.featureSettingsProvider = featureSettingsProvider; 25 | } 26 | 27 | /** 28 | * @desription Returns true if the feature is enabled 29 | * Fetches and instantiates the feature implementation and checks if 30 | * that feature is enabled. If no feature is found or cannot be 31 | * instantiated the default return is false 32 | * 33 | * @param featureName name of the feature to be checked 34 | * @return true if enabled 35 | */ 36 | public Boolean isEnabled(String featureName) { 37 | FeatureSetting featureSetting = this.featureSettingsProvider.getFeatureSettingByName( 38 | featureName 39 | ); 40 | 41 | if (featureSetting == null) { 42 | return false; 43 | } 44 | 45 | return featureSetting.implementation.isEnabled(featureName); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/feature-mgmt/core/main/classes/FeatureCheckerImplementation.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 52.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/feature-mgmt/core/main/classes/FeatureSetting.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * @description Apex Representation of a Feature Setting 3 | */ 4 | public class FeatureSetting { 5 | /** @description featureName */ 6 | public String featureName; 7 | 8 | /** @description Feature Implementation */ 9 | public Feature implementation; 10 | 11 | /** 12 | * @description Constructor 13 | * 14 | * @param featureName 15 | * @param implementation 16 | */ 17 | public FeatureSetting(String featureName, String implementation) { 18 | this.featureName = featureName; 19 | this.implementation = this.getImplementation(implementation); 20 | } 21 | 22 | private Feature getImplementation(String implementationName) { 23 | if (String.isBlank(implementationName)) { 24 | throw new IllegalArgumentException( 25 | 'Implementation cannot be blank' 26 | ); 27 | } 28 | 29 | Type implementationType = Type.forName(implementationName); 30 | if (implementationType == null) { 31 | throw new IllegalArgumentException('Implementation is not a Type'); 32 | } 33 | 34 | try { 35 | Object obj = implementationType.newInstance(); 36 | return (Feature) obj; 37 | } catch (Exception ex) { 38 | throw new IllegalArgumentException( 39 | 'Implementation cannot be instantiated or is not a feature', 40 | ex 41 | ); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/feature-mgmt/core/main/classes/FeatureSetting.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 52.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/feature-mgmt/core/main/classes/FeatureSettingsProvider.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * @description Feature Settings Provider 3 | * Returns the feature settings 4 | */ 5 | public interface FeatureSettingsProvider { 6 | /** 7 | * @description Returns Feature Setting Object by Feature Name 8 | * if not found should return null 9 | * @param featureName 10 | * @return Feature Setting Object null if not found 11 | */ 12 | FeatureSetting getFeatureSettingByName(String featureName); 13 | } 14 | -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/feature-mgmt/core/main/classes/FeatureSettingsProvider.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 52.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/feature-mgmt/core/main/objects/FeatureSetting__mdt/FeatureSetting__mdt.object-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Mapping of a feature to its given implementation to check if its enabled 5 | 6 | Feature Settings 7 | Public 8 | 9 | -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/feature-mgmt/core/main/objects/FeatureSetting__mdt/fields/Implementation__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Implementation__c 4 | Class Name for the feature implementation 5 | false 6 | DeveloperControlled 7 | Class Name for the feature implementation 8 | 9 | 50 10 | true 11 | Text 12 | false 13 | 14 | -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/feature-mgmt/core/test/classes/CustMetadataFeatureSettingsProviderTest.cls: -------------------------------------------------------------------------------- 1 | @IsTest(IsParallel=true) 2 | private class CustMetadataFeatureSettingsProviderTest { 3 | @IsTest 4 | private static void shouldReturnNullWithMissingMetadata() { 5 | String featureName = 'myTestFeature'; 6 | FeatureSettingsProvider settingsProvider = new CustomMetadataFeatureSettingsProvider(); 7 | FeatureSetting setting = settingsProvider.getFeatureSettingByName( 8 | featureName 9 | ); 10 | 11 | System.assert(setting == null, 'Setting Should be Null'); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/feature-mgmt/core/test/classes/CustMetadataFeatureSettingsProviderTest.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 54.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/feature-mgmt/core/test/classes/FeatureCheckerImplementationTest.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 52.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/feature-mgmt/core/test/classes/FeatureSettingTest.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 52.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/feature-mgmt/features/main/classes/AlwaysDisabledFeature.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * @description Feature Implementation that is always enabled 3 | */ 4 | public class AlwaysDisabledFeature implements Feature { 5 | /** 6 | * @description returns false for all features 7 | * 8 | * @param featureName 9 | * @return true 10 | */ 11 | public Boolean isEnabled(String featureName) { 12 | return false; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/feature-mgmt/features/main/classes/AlwaysDisabledFeature.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 52.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/feature-mgmt/features/main/classes/AlwaysEnabledFeature.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * @description Feature Implementation that is always enabled 3 | */ 4 | public class AlwaysEnabledFeature implements Feature { 5 | /** 6 | * @description returns true for all features 7 | * 8 | * @param featureName 9 | * @return true 10 | */ 11 | public Boolean isEnabled(String featureName) { 12 | return true; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/feature-mgmt/features/main/classes/AlwaysEnabledFeature.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 52.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/feature-mgmt/features/main/classes/CustomMetadataEnabledFeature.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * @description Feature Implementation that uses a global 3 | * custom metadata flag. 4 | */ 5 | public class CustomMetadataEnabledFeature implements Feature { 6 | /** 7 | * @description checks if the feature has been enabled via 8 | * a custom metadata flag 9 | * 10 | * @param featureName 11 | * @return true if enabled 12 | */ 13 | public Boolean isEnabled(String featureName) { 14 | FeatureFlag__mdt feature = FeatureFlag__mdt.getInstance(featureName); 15 | 16 | if (feature == null) { 17 | return false; 18 | } 19 | 20 | return feature.Enabled__c; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/feature-mgmt/features/main/classes/CustomMetadataEnabledFeature.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 52.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/feature-mgmt/features/main/classes/CustomPermissionEnabledFeature.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * @description Feature Implementation that checks the running user for a custom permission 3 | */ 4 | public class CustomPermissionEnabledFeature implements Feature { 5 | /** 6 | * @description checks if the feature has been enabled via 7 | * a custom permission 8 | * 9 | * @param featureName 10 | * @return true if enabled 11 | */ 12 | public Boolean isEnabled(String featureName) { 13 | if (String.isBlank(featureName)) { 14 | throw new IllegalArgumentException('feature cannot be blank'); 15 | } 16 | 17 | if (featureName.containsWhitespace()) { 18 | throw new IllegalArgumentException( 19 | 'feature cannot contain white spaces' 20 | ); 21 | } 22 | 23 | return FeatureManagement.checkPermission(featureName); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/feature-mgmt/features/main/classes/CustomPermissionEnabledFeature.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 52.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/feature-mgmt/features/main/objects/FeatureFlag__mdt/FeatureFlag__mdt.object-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Global Feature Flags used by the Custom Metadata Feature Implementation 5 | 6 | Feature Flags 7 | Public 8 | 9 | -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/feature-mgmt/features/main/objects/FeatureFlag__mdt/fields/Enabled__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Enabled__c 4 | false 5 | If true the feature has been enabled 6 | false 7 | DeveloperControlled 8 | If true the feature has been enabled 9 | 10 | Checkbox 11 | 12 | -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/feature-mgmt/features/test/classes/AlwaysDisabledFeatureTest.cls: -------------------------------------------------------------------------------- 1 | @IsTest(IsParallel=true) 2 | private class AlwaysDisabledFeatureTest { 3 | @IsTest 4 | private static void shouldAlwaysReturnFalse() { 5 | String featureName = 'feature'; 6 | Feature disabledFeature = new AlwaysDisabledFeature(); 7 | 8 | System.assert( 9 | !disabledFeature.isEnabled(featureName), 10 | 'Feature should not be enabled' 11 | ); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/feature-mgmt/features/test/classes/AlwaysDisabledFeatureTest.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 52.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/feature-mgmt/features/test/classes/AlwaysEnabledFeatureTest.cls: -------------------------------------------------------------------------------- 1 | @IsTest(IsParallel=true) 2 | private class AlwaysEnabledFeatureTest { 3 | @IsTest 4 | private static void shouldAlwaysReturnTrue() { 5 | String featureName = 'myFeature'; 6 | Feature enabledFeature = new AlwaysEnabledFeature(); 7 | 8 | System.assert( 9 | enabledFeature.isEnabled(featureName), 10 | 'Feature should be enabled' 11 | ); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/feature-mgmt/features/test/classes/AlwaysEnabledFeatureTest.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 52.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/feature-mgmt/features/test/classes/CustomMetadataEnabledFeatureTest.cls: -------------------------------------------------------------------------------- 1 | @IsTest(IsParallel=true) 2 | private class CustomMetadataEnabledFeatureTest { 3 | @IsTest 4 | private static void shouldReturnFalseIfNullFeature() { 5 | String featureName = 'My Test Feature'; 6 | Feature disabledFeature = new CustomMetadataEnabledFeature(); 7 | 8 | System.assert( 9 | !disabledFeature.isEnabled(featureName), 10 | 'Feature should be disabled' 11 | ); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/feature-mgmt/features/test/classes/CustomMetadataEnabledFeatureTest.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 52.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /packages/apexlink/tests/resources/feature-mgmt/features/test/classes/CustomPermissionEnabledFeatureTest.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 52.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /packages/apexlink/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "experimentalDecorators": true, 5 | "outDir": "./lib", 6 | "rootDir": "./src", 7 | "skipLibCheck": true, 8 | "declarationMap": true 9 | }, 10 | "exclude": ["node_modules", "dist", "tests"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/core/.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | root = false 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /packages/core/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # [33.23.0](https://github.com/dxatscale/sfpowerscripts/compare/@dxatscale/sfpowerscripts.core@33.22.1...@dxatscale/sfpowerscripts.core@33.23.0) (2023-03-22) 7 | 8 | 9 | ### Features 10 | 11 | * **publish:** Add new flag to delete Git tags by age and limit ([#1275](https://github.com/dxatscale/sfpowerscripts/issues/1275)) ([aae62d6](https://github.com/dxatscale/sfpowerscripts/commit/aae62d6d3e7eb390dddcf2ca46b99b44ca4cc933)) 12 | -------------------------------------------------------------------------------- /packages/core/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | plugins: [ 4 | '@babel/plugin-proposal-optional-chaining', 5 | '@babel/plugin-proposal-nullish-coalescing-operator' 6 | ], 7 | }; -------------------------------------------------------------------------------- /packages/core/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest/presets/js-with-babel', 3 | testEnvironment: 'node', 4 | restoreMocks: true, 5 | clearMocks: true, 6 | resetMocks: true, 7 | transform: { 8 | '^.+\\.[t]sx?$': [ 9 | 'ts-jest', 10 | { 11 | tsconfig: 'tsconfig.json', 12 | babelConfig: true, 13 | }, 14 | ] 15 | }, 16 | transformIgnorePatterns: ['/node_modules/(?!@salesforce/source-deploy-retrieve)(.*)'], 17 | moduleNameMapper: { 18 | '^axios$': require.resolve('axios'), 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /packages/core/src/apex/ApexClassFetcher.ts: -------------------------------------------------------------------------------- 1 | import { Connection } from '@salesforce/core'; 2 | import chunkCollection from "../queryHelper/ChunkCollection"; 3 | import QueryHelper from '../queryHelper/QueryHelper'; 4 | 5 | export default class ApexClassFetcher { 6 | constructor(private conn: Connection) {} 7 | 8 | /** 9 | * Query Apex Classes by Name 10 | * 11 | * @param classNames 12 | * @returns 13 | */ 14 | public async fetchApexClassByName(classNames: string[]): Promise<{ Id: string; Name: string }[]> { 15 | let result: {Id: string; Name: string}[] = []; 16 | 17 | const chunks = chunkCollection(classNames); 18 | for (const chunk of chunks) { 19 | const formattedChunk = chunk.map(elem => `'${elem}'`).toString(); // transform into formatted string for query 20 | const query = `SELECT ID, Name FROM ApexClass WHERE Name IN (${formattedChunk})`; 21 | 22 | const records = await QueryHelper.query<{ Id: string; Name: string }>(query, this.conn, false); 23 | result = result.concat(records); 24 | } 25 | 26 | return result; 27 | } 28 | } -------------------------------------------------------------------------------- /packages/core/src/apex/ApexTriggerFetcher.ts: -------------------------------------------------------------------------------- 1 | import { Connection } from '@salesforce/core'; 2 | import chunkCollection from '../queryHelper/ChunkCollection'; 3 | import QueryHelper from '../queryHelper/QueryHelper'; 4 | 5 | export default class ApexTriggerFetcher { 6 | constructor(private conn: Connection) {} 7 | 8 | /** 9 | * Query Triggers by Name 10 | * 11 | * @param triggerNames 12 | * @returns 13 | */ 14 | public async fetchApexTriggerByName(triggerNames: string[]): Promise<{ Id: string; Name: string }[]> { 15 | let result: {Id: string, Name: string}[] = []; 16 | 17 | const chunks = chunkCollection(triggerNames); 18 | for (const chunk of chunks) { 19 | const formattedChunk = chunk.map(elem => `'${elem}'`).toString(); // transform into formatted string for query 20 | const query = `SELECT ID, Name FROM ApexTrigger WHERE Name IN (${formattedChunk})`; 21 | 22 | const records = await QueryHelper.query<{ Id: string; Name: string }>(query, this.conn, false); 23 | result = result.concat(records); 24 | } 25 | 26 | return result; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/core/src/apex/coverage/ApexCodeCoverageAggregateFetcher.ts: -------------------------------------------------------------------------------- 1 | import { Connection } from '@salesforce/core'; 2 | import chunkCollection from '../../queryHelper/ChunkCollection'; 3 | import QueryHelper from '../../queryHelper/QueryHelper'; 4 | 5 | export default class ApexCodeCoverageAggregateFetcher { 6 | constructor(private conn: Connection) {} 7 | 8 | /** 9 | * Query ApexCodeCoverageAggregate by list of ApexClassorTriggerId 10 | * @param listOfApexClassOrTriggerId 11 | * @returns 12 | */ 13 | public async fetchACCAById(listOfApexClassOrTriggerId: string[]): Promise<{ 14 | ApexClassOrTriggerId: string; 15 | NumLinesCovered: number; 16 | NumLinesUncovered: number; 17 | Coverage: any; 18 | }[]> { 19 | let result: { 20 | ApexClassOrTriggerId: string; 21 | NumLinesCovered: number; 22 | NumLinesUncovered: number; 23 | Coverage: any; 24 | }[] = []; 25 | 26 | const chunks = chunkCollection(listOfApexClassOrTriggerId); 27 | for (const chunk of chunks) { 28 | const formattedChunk = chunk.map(elem => `'${elem}'`).toString(); 29 | let query = `SELECT ApexClassorTriggerId, NumLinesCovered, NumLinesUncovered, Coverage FROM ApexCodeCoverageAggregate WHERE ApexClassorTriggerId IN (${formattedChunk})`; 30 | 31 | const records = await QueryHelper.query<{ 32 | ApexClassOrTriggerId: string; 33 | NumLinesCovered: number; 34 | NumLinesUncovered: number; 35 | Coverage: any; 36 | }>(query, this.conn, true); 37 | result = result.concat(records); 38 | } 39 | 40 | return result; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/core/src/apex/parser/listeners/ApexTypeListener.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ApexParserListener, 3 | AnnotationContext, 4 | InterfaceDeclarationContext, 5 | ClassDeclarationContext, 6 | } from 'apex-parser'; 7 | 8 | export default class ApexTypeListener implements ApexParserListener { 9 | private apexType: ApexType = { 10 | class: false, 11 | testClass: false, 12 | interface: false, 13 | }; 14 | 15 | enterAnnotation(ctx: AnnotationContext): void { 16 | if (ctx.text.toUpperCase().startsWith('@ISTEST')) { 17 | this.apexType.testClass= true; 18 | } 19 | } 20 | 21 | enterInterfaceDeclaration(ctx: InterfaceDeclarationContext): void { 22 | this.apexType.interface = true; 23 | } 24 | 25 | enterClassDeclaration(ctx: ClassDeclarationContext): void { 26 | this.apexType.class = true; 27 | } 28 | 29 | public getApexType(): ApexType { 30 | return this.apexType; 31 | } 32 | } 33 | 34 | interface ApexType { 35 | class: boolean; 36 | testClass: boolean; 37 | interface: boolean; 38 | } 39 | -------------------------------------------------------------------------------- /packages/core/src/apextest/ApexTestSuite.ts: -------------------------------------------------------------------------------- 1 | 2 | const fs = require('fs-extra'); 3 | import path from 'path'; 4 | import xml2json from '../utils/xml2json'; 5 | import { globSync } from 'glob'; 6 | 7 | export default class ApexTestSuite { 8 | public constructor(private sourceDir: string, private suiteName: string) {} 9 | 10 | public async getConstituentClasses(): Promise { 11 | let testSuitePaths: string[] = globSync(`**${this.suiteName}.testSuite-meta.xml`, { 12 | cwd: this.sourceDir, 13 | absolute: true, 14 | }); 15 | 16 | console.log('testSuitePaths',testSuitePaths); 17 | 18 | if (!testSuitePaths[0]) throw new Error(`Apex Test Suite ${this.suiteName} not found`); 19 | 20 | let apex_test_suite: any = await xml2json(fs.readFileSync(path.resolve(testSuitePaths[0]))); 21 | 22 | if (Array.isArray(apex_test_suite.ApexTestSuite.testClassName)) { 23 | return apex_test_suite.ApexTestSuite.testClassName; 24 | } else { 25 | let testClassess = new Array(); 26 | testClassess.push(apex_test_suite.ApexTestSuite.testClassName); 27 | return testClassess; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/core/src/changelog/interfaces/GenericChangelogInterfaces.ts: -------------------------------------------------------------------------------- 1 | export interface Changelog { 2 | /** 3 | * Name of the package 4 | */ 5 | name: string; 6 | 7 | /** 8 | * Backwards-compatibility for delta package 9 | */ 10 | from: string; 11 | 12 | /** 13 | * Commit Id from which package was created 14 | * May not necessarily be the first element in commits 15 | */ 16 | to: string; 17 | 18 | /** 19 | * Commits that modified the package 20 | */ 21 | commits: Commit[]; 22 | } 23 | 24 | export interface Commit { 25 | commitId: string; 26 | date: string; 27 | author: string; 28 | message: string; 29 | body: string; 30 | } 31 | -------------------------------------------------------------------------------- /packages/core/src/dependency/Component.ts: -------------------------------------------------------------------------------- 1 | import { PackageType } from "../package/SfpPackage"; 2 | 3 | /** 4 | * Component details and package it belongs to 5 | */ 6 | export default interface Component { 7 | id: string; 8 | fullName: string; 9 | type: string; 10 | files?: string[]; 11 | package?: string; 12 | packageType?: PackageType; 13 | indexOfPackage?: number; 14 | namespace?: string; 15 | dependencies?: Component[]; 16 | } 17 | -------------------------------------------------------------------------------- /packages/core/src/dependency/DependencyViolation.ts: -------------------------------------------------------------------------------- 1 | import Component from './Component'; 2 | 3 | export default interface DependencyViolation { 4 | component: Component; 5 | dependency: any; 6 | description: string; 7 | } 8 | -------------------------------------------------------------------------------- /packages/core/src/dependency/Entrypoint.ts: -------------------------------------------------------------------------------- 1 | import Component from './Component'; 2 | 3 | /** 4 | * Used by sfdc-soup API calls 5 | */ 6 | export default interface Entrypoint { 7 | name: string; 8 | type: string; 9 | id: string; 10 | } 11 | 12 | export function component2entrypoint(components: Component[]): Entrypoint[] { 13 | return components.map((component) => { 14 | return { 15 | name: component.fullName, 16 | type: component.type, 17 | id: component.id, 18 | } as Entrypoint; 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /packages/core/src/deployers/DeploymentExecutor.ts: -------------------------------------------------------------------------------- 1 | import { MetadataApiDeployStatus } from "@salesforce/source-deploy-retrieve"; 2 | 3 | export default interface DeploymentExecutor { 4 | exec(): Promise; 5 | } 6 | 7 | export interface DeploySourceResult { 8 | deploy_id: string; 9 | result: boolean; 10 | message: string; 11 | response?:MetadataApiDeployStatus 12 | } 13 | 14 | export enum DeploymentType { 15 | SOURCE_PUSH, 16 | MDAPI_DEPLOY, 17 | } 18 | -------------------------------------------------------------------------------- /packages/core/src/display/DependencyViolationDisplayer.ts: -------------------------------------------------------------------------------- 1 | const Table = require('cli-table'); 2 | import DependencyViolation from '../dependency/DependencyViolation'; 3 | import SFPLogger from '@dxatscale/sfp-logger'; 4 | import { ZERO_BORDER_TABLE } from './TableConstants'; 5 | 6 | export default class DependencyViolationDisplayer { 7 | public static printDependencyViolations(dependencyViolations: DependencyViolation[]) { 8 | if (!dependencyViolations || dependencyViolations.length === 0) return; 9 | 10 | const table = new Table({ 11 | head: ['API Name', 'Type', 'Package', 'Dependency', 'Dependency Type', 'Dependency Package', 'Problem'], 12 | chars: ZERO_BORDER_TABLE 13 | }); 14 | 15 | SFPLogger.log('The following components resulted in failures:'); 16 | 17 | dependencyViolations.forEach((violation) => { 18 | table.push([ 19 | violation.component.fullName, 20 | violation.component.type, 21 | violation.component.package, 22 | violation.dependency.fullName, 23 | violation.dependency.type, 24 | violation.dependency.package, 25 | violation.description, 26 | ]); 27 | }); 28 | 29 | SFPLogger.log(table.toString()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/core/src/display/ExternalDependencyDisplayer.ts: -------------------------------------------------------------------------------- 1 | import SFPLogger, { COLOR_KEY_MESSAGE, Logger, LoggerLevel } from '@dxatscale/sfp-logger'; 2 | import { EOL } from 'os'; 3 | import Package2Detail from '../package/Package2Detail'; 4 | import { ZERO_BORDER_TABLE } from './TableConstants'; 5 | const Table = require('cli-table'); 6 | 7 | export default class ExternalDependencyDisplayer { 8 | public constructor(private externalPackage2s: Package2Detail[], private logger: Logger) {} 9 | 10 | public display() { 11 | if (this.externalPackage2s.length > 0) { 12 | let table = new Table({ 13 | head: ['Order', 'Package', 'Version', 'Subscriber Version Id'], 14 | chars: ZERO_BORDER_TABLE 15 | }); 16 | let i = 0; 17 | for (const externalPackage of this.externalPackage2s) { 18 | table.push([ 19 | i++, 20 | externalPackage.name, 21 | externalPackage.versionNumber ? externalPackage.versionNumber : 'N/A', 22 | externalPackage.subscriberPackageVersionId ? externalPackage.subscriberPackageVersionId:'N/A, Could be part of current operation', 23 | ]); 24 | } 25 | SFPLogger.log(EOL, LoggerLevel.INFO, this.logger); 26 | SFPLogger.log(COLOR_KEY_MESSAGE(`Resolved external package dependencies:`), LoggerLevel.INFO, this.logger); 27 | SFPLogger.log(table.toString(), LoggerLevel.INFO, this.logger); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /packages/core/src/display/InstalledArtifactsDisplayer.ts: -------------------------------------------------------------------------------- 1 | const Table = require('cli-table'); 2 | import SFPLogger, { Logger, LoggerLevel, COLOR_KEY_MESSAGE } from '@dxatscale/sfp-logger'; 3 | import { ZERO_BORDER_TABLE } from './TableConstants'; 4 | 5 | export default class InstalledArtifactsDisplayer { 6 | public static printInstalledArtifacts(artifacts: any, logger: Logger) { 7 | if (!artifacts) return; 8 | else if(artifacts.length==0) return; 9 | 10 | let table = new Table({ 11 | head: ['Artifact', 'Version', 'Commit Id'], 12 | chars: ZERO_BORDER_TABLE 13 | }); 14 | 15 | artifacts.forEach((artifact) => { 16 | table.push([artifact.Name, artifact.Version__c, artifact.CommitId__c?artifact.CommitId__c:""]); 17 | }); 18 | 19 | SFPLogger.log(COLOR_KEY_MESSAGE('Artifacts installed in org:'), LoggerLevel.INFO, logger); 20 | SFPLogger.log(table.toString(), LoggerLevel.INFO, logger); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/core/src/display/InstalledPackagesDisplayer.ts: -------------------------------------------------------------------------------- 1 | const Table = require('cli-table'); 2 | import SFPLogger, { Logger, LoggerLevel, COLOR_KEY_MESSAGE } from '@dxatscale/sfp-logger'; 3 | import Package2Detail from '../package/Package2Detail'; 4 | import { ZERO_BORDER_TABLE } from './TableConstants'; 5 | 6 | export default class InstalledPackagesDisplayer { 7 | public static printInstalledPackages(packages: Package2Detail[], logger: Logger) { 8 | if (packages == null) return; 9 | 10 | let table = new Table({ 11 | head: ['Package', 'Version', 'Type', 'isOrgDependent'], 12 | chars: ZERO_BORDER_TABLE 13 | }); 14 | 15 | packages.forEach((pkg) => { 16 | table.push([pkg.name, pkg.versionNumber, pkg.type, pkg.isOrgDependent]); 17 | }); 18 | 19 | SFPLogger.log(COLOR_KEY_MESSAGE('Packages installed in org:'), LoggerLevel.INFO, logger); 20 | SFPLogger.log(table.toString(), LoggerLevel.INFO, logger); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/core/src/display/PackageComponentPrinter.ts: -------------------------------------------------------------------------------- 1 | const Table = require('cli-table'); 2 | import { LazyCollection, SourceComponent } from '@salesforce/source-deploy-retrieve'; 3 | import SFPLogger, { Logger, LoggerLevel } from '@dxatscale/sfp-logger'; 4 | import { ZERO_BORDER_TABLE } from './TableConstants'; 5 | 6 | export default class PackageComponentPrinter { 7 | public static printComponentTable(components: LazyCollection, logger: Logger) { 8 | //If Manifest is null, just return 9 | if (components === null || components === undefined) return; 10 | 11 | let table = new Table({ 12 | head: ['Metadata Type', 'API Name'], 13 | chars: ZERO_BORDER_TABLE 14 | }); 15 | 16 | let componentArray = components.toArray(); 17 | componentArray.sort((a, b) => a.type.name.localeCompare(b.type.name)); 18 | 19 | for (const component of componentArray) { 20 | let item = [component.type.name, component.fullName]; 21 | table.push(item); 22 | } 23 | 24 | SFPLogger.log('The following metadata will be deployed:', LoggerLevel.INFO, logger); 25 | SFPLogger.log(table.toString(), LoggerLevel.INFO, logger); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/core/src/display/PackageDependencyDisplayer.ts: -------------------------------------------------------------------------------- 1 | import SFPLogger, { Logger, LoggerLevel } from '@dxatscale/sfp-logger'; 2 | import { ZERO_BORDER_TABLE } from './TableConstants'; 3 | const Table = require('cli-table'); 4 | 5 | export default class PackageDependencyDisplayer { 6 | public static printPackageDependencies( 7 | dependencies: { package: string; versionNumber?: string }[], 8 | projectConfig: any, 9 | logger: Logger 10 | ) { 11 | if (Array.isArray(dependencies)) { 12 | SFPLogger.log('Package Dependencies:', LoggerLevel.INFO, logger); 13 | const table = new Table({ 14 | head: ['Order','Package', 'Version'], 15 | chars: ZERO_BORDER_TABLE, 16 | style: { 'padding-left': 3 }, 17 | }); 18 | let order=1; 19 | for (const dependency of dependencies) { 20 | let versionNumber = 'N/A'; 21 | 22 | if (!dependency.versionNumber) 23 | versionNumber = projectConfig.packageAliases[dependency.package] 24 | ? projectConfig.packageAliases[dependency.package] 25 | : 'N/A'; 26 | else versionNumber = dependency.versionNumber; 27 | 28 | const row = [order,dependency.package, versionNumber]; 29 | table.push(row); 30 | order++; 31 | } 32 | SFPLogger.log(table.toString(), LoggerLevel.INFO, logger); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/core/src/display/PackageMetadataPrinter.ts: -------------------------------------------------------------------------------- 1 | const Table = require('cli-table'); 2 | import SFPLogger, { Logger, LoggerLevel } from '@dxatscale/sfp-logger'; 3 | import { ZERO_BORDER_TABLE } from './TableConstants'; 4 | 5 | export default class PackageMetadataPrinter { 6 | public static printMetadataToDeploy(payload: any, logger: Logger) { 7 | //If Manifest is null, just return 8 | if (payload === null || payload === undefined) return; 9 | 10 | let table = new Table({ 11 | head: ['Metadata Type', 'API Name'], 12 | chars: ZERO_BORDER_TABLE 13 | }); 14 | 15 | let pushTypeMembersIntoTable = (type) => { 16 | if (type['members'] instanceof Array) { 17 | for (let member of type['members']) { 18 | let item = [type.name, member]; 19 | table.push(item); 20 | } 21 | } else { 22 | let item = [type.name, type.members]; 23 | table.push(item); 24 | } 25 | }; 26 | 27 | if (payload['Package']['types'] instanceof Array) { 28 | for (let type of payload['Package']['types']) { 29 | pushTypeMembersIntoTable(type); 30 | } 31 | } else { 32 | let type = payload['Package']['types']; 33 | pushTypeMembersIntoTable(type); 34 | } 35 | SFPLogger.log('The following metadata will be deployed:', LoggerLevel.INFO, logger); 36 | SFPLogger.log(table.toString(), LoggerLevel.INFO, logger); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/core/src/display/TableConstants.ts: -------------------------------------------------------------------------------- 1 | 2 | export const ZERO_BORDER_TABLE = { 3 | top: ' ', 4 | 'top-mid': ' ', 5 | 'top-left': ' ', 6 | 'top-right': ' ', 7 | bottom: ' ', 8 | 'bottom-mid': ' ', 9 | 'bottom-left': ' ', 10 | 'bottom-right': ' ', 11 | left: '', 12 | 'left-mid': '', 13 | mid: '', 14 | 'mid-mid': '', 15 | right: '', 16 | 'right-mid': '', 17 | middle: ' ', 18 | }; 19 | 20 | 21 | 22 | export const COLON_MIDDLE_BORDER_TABLE = { 23 | top: '', 24 | 'top-mid': '', 25 | 'top-left': '', 26 | 'top-right': '', 27 | bottom: '', 28 | 'bottom-mid': '', 29 | 'bottom-left': '', 30 | 'bottom-right': '', 31 | left: '', 32 | 'left-mid': '', 33 | mid: '', 34 | 'mid-mid': '', 35 | right: '', 36 | 'right-mid': '', 37 | middle: ':', 38 | }; 39 | -------------------------------------------------------------------------------- /packages/core/src/git/GitIdentity.ts: -------------------------------------------------------------------------------- 1 | import { SimpleGit } from 'simple-git/promise'; 2 | 3 | export default class GitIdentity { 4 | constructor(private git: SimpleGit) {} 5 | 6 | async setUsernameAndEmail(): Promise { 7 | await this.setUsername(); 8 | await this.setEmail(); 9 | } 10 | 11 | private async setUsername(): Promise { 12 | let username: string; 13 | 14 | if (process.env.SFPOWERSCRIPTS_GIT_USERNAME) { 15 | username = process.env.SFPOWERSCRIPTS_GIT_USERNAME; 16 | } else { 17 | username = 'sfpowerscripts'; 18 | } 19 | 20 | await this.git.addConfig('user.name', username); 21 | } 22 | 23 | private async setEmail(): Promise { 24 | let email: string; 25 | 26 | if (process.env.SFPOWERSCRIPTS_GIT_EMAIL) { 27 | email = process.env.SFPOWERSCRIPTS_GIT_EMAIL; 28 | } else { 29 | email = 'sfpowerscripts@dxatscale.io'; 30 | } 31 | 32 | await this.git.addConfig('user.email', email); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/core/src/ignore/IgnoreFiles.ts: -------------------------------------------------------------------------------- 1 | import ignore, { Ignore } from 'ignore'; 2 | 3 | export default class IgnoreFiles { 4 | private _ignore: Ignore; 5 | 6 | constructor(pattern: string) { 7 | this._ignore = ignore().add(pattern); 8 | } 9 | 10 | filter(pathnames: string[]): string[] { 11 | return this._ignore.filter(pathnames); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/core/src/index.ts: -------------------------------------------------------------------------------- 1 | export default {}; 2 | -------------------------------------------------------------------------------- /packages/core/src/limits/LimitsFetcher.ts: -------------------------------------------------------------------------------- 1 | import { Connection } from '@salesforce/core'; 2 | const retry = require('async-retry'); 3 | 4 | export default class LimitsFetcher { 5 | constructor(private conn: Connection) {} 6 | 7 | public async getApiLimits() { 8 | const limits: { name: string; max: number; remaining: number }[] = []; 9 | const endpoint = `${this.conn.instanceUrl}/services/data/v${this.conn.version}/limits`; 10 | 11 | const result = await retry( 12 | async (bail) => { 13 | return this.conn.request<{ 14 | [p: string]: { Max: number; Remaining: number }; 15 | }>(endpoint); 16 | }, 17 | { retries: 3, minTimeout: 2000 } 18 | ); 19 | 20 | Object.keys(result).forEach((limitName) => { 21 | limits.push({ 22 | name: limitName, 23 | max: result[limitName].Max, 24 | remaining: result[limitName].Remaining, 25 | }); 26 | }); 27 | 28 | return limits; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/core/src/metadata/SettingsFetcher.ts: -------------------------------------------------------------------------------- 1 | import SFPLogger, { Logger, LoggerLevel } from '@dxatscale/sfp-logger'; 2 | import SFPOrg from '../org/SFPOrg'; 3 | const fs = require('fs-extra'); 4 | import { XMLParser } from 'fast-xml-parser'; 5 | import MetadataFetcher from './MetadataFetcher'; 6 | 7 | export default class SettingsFetcher extends MetadataFetcher { 8 | constructor(logger: Logger) { 9 | super(logger); 10 | } 11 | 12 | public async getSetttingMetadata(org: SFPOrg, setting: string) { 13 | SFPLogger.log(`Fetching ${setting}Settings from Org`, LoggerLevel.INFO, this.logger); 14 | let retriveLocation = (await this.fetchPackageFromOrg(org, { 15 | types: { name: 'Settings', members: setting }, 16 | })).unzippedLocation; 17 | let resultFile = `${retriveLocation}/settings/${setting}.settings`; 18 | const parser = new XMLParser(); 19 | let parsedSettings = parser.parse(fs.readFileSync(resultFile).toString())[`${setting}Settings`]; 20 | return parsedSettings; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/core/src/org/OrganizationFetcher.ts: -------------------------------------------------------------------------------- 1 | import { Connection } from '@salesforce/core'; 2 | import QueryHelper from '../queryHelper/QueryHelper'; 3 | 4 | export default class OrganizationFetcher { 5 | constructor(private conn: Connection) {} 6 | 7 | public fetch() { 8 | const query = 'SELECT OrganizationType, IsSandbox FROM Organization LIMIT 1'; 9 | 10 | return QueryHelper.query<{ OrganizationType: string; IsSandbox: boolean }>(query, this.conn, false); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/core/src/org/ScratchOrgInfoFetcher.ts: -------------------------------------------------------------------------------- 1 | import { Org, trimTo15 } from '@salesforce/core'; 2 | import QueryHelper from '../queryHelper/QueryHelper'; 3 | 4 | export default class ScratchOrgInfoFetcher { 5 | constructor(private hubOrg: Org) {} 6 | 7 | public async getScratchOrgInfoByOrgId(orgId: string[]) { 8 | const conn = this.hubOrg.getConnection(); 9 | 10 | let collection = orgId 11 | .map((id) => { 12 | return `'${trimTo15(id)}'`; 13 | }) 14 | .toString(); 15 | 16 | let query = ` 17 | SELECT Id, ScratchOrg, Status 18 | FROM ScratchOrgInfo 19 | WHERE ScratchOrg IN (${collection}) 20 | `; 21 | 22 | return QueryHelper.query(query, conn, false); 23 | } 24 | } 25 | 26 | export interface ScratchOrgInfo { 27 | Id: string; 28 | ScratchOrg: string; 29 | Status: 'New' | 'Deleted' | 'Active' | 'Error'; 30 | } 31 | -------------------------------------------------------------------------------- /packages/core/src/org/packageQuery/InstalledPackagesQueryExecutor.ts: -------------------------------------------------------------------------------- 1 | import { Connection } from '@salesforce/core'; 2 | import QueryHelper from '../../queryHelper/QueryHelper'; 3 | 4 | export default class InstalledPackagesQueryExecutor { 5 | static async exec(conn: Connection) { 6 | const installedPackagesQuery = 7 | 'SELECT Id, SubscriberPackageId, SubscriberPackage.NamespacePrefix, SubscriberPackage.Name, ' + 8 | 'SubscriberPackageVersion.Id, SubscriberPackageVersion.Name, SubscriberPackageVersion.MajorVersion, SubscriberPackageVersion.MinorVersion, ' + 9 | 'SubscriberPackageVersion.PatchVersion, SubscriberPackageVersion.BuildNumber, SubscriberPackageVersion.Package2ContainerOptions, SubscriberPackageVersion.IsOrgDependent FROM InstalledSubscriberPackage ' + 10 | 'ORDER BY SubscriberPackageId'; 11 | 12 | return QueryHelper.query(installedPackagesQuery, conn, true); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/core/src/package/Package2Detail.ts: -------------------------------------------------------------------------------- 1 | export default interface Package2Detail { 2 | name: string; 3 | package2Id?: string; 4 | namespacePrefix?: string; 5 | subscriberPackageVersionId?: string; 6 | versionNumber?: string; 7 | type?: string; 8 | isOrgDependent?: boolean; 9 | key?: string; 10 | } 11 | -------------------------------------------------------------------------------- /packages/core/src/package/analyser/AnalyzerRegistry.ts: -------------------------------------------------------------------------------- 1 | import FHTAnalyser from './FHTAnalyzer'; 2 | import FTAnalyser from './FTAnalyzer'; 3 | import { PackageAnalyzer } from './PackageAnalyzer'; 4 | import PicklistAnalyzer from './PicklistAnalyzer'; 5 | 6 | export class AnalyzerRegistry { 7 | static getAnalyzers(): PackageAnalyzer[] { 8 | let packageAnalyzers: PackageAnalyzer[] = []; 9 | 10 | //TODO: Make dynamic 11 | let fhtAnalyzer = new FHTAnalyser(); 12 | let ftAnalyser = new FTAnalyser(); 13 | let picklistAnalyzer = new PicklistAnalyzer(); 14 | packageAnalyzers.push(fhtAnalyzer); 15 | packageAnalyzers.push(ftAnalyser); 16 | packageAnalyzers.push(picklistAnalyzer); 17 | 18 | return packageAnalyzers; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/core/src/package/analyser/PackageAnalyzer.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from "@dxatscale/sfp-logger"; 2 | import { ComponentSet } from "@salesforce/source-deploy-retrieve"; 3 | import SfpPackage from "../SfpPackage"; 4 | 5 | export interface PackageAnalyzer 6 | { 7 | getName(); 8 | analyze(sfpPackage: SfpPackage,componentSet:ComponentSet,logger:Logger): Promise 9 | isEnabled(sfpPackage: SfpPackage,logger:Logger): Promise 10 | 11 | 12 | } -------------------------------------------------------------------------------- /packages/core/src/package/components/MetadataCount.ts: -------------------------------------------------------------------------------- 1 | import { globSync } from 'glob'; 2 | import path from 'path'; 3 | 4 | export default class MetadataCount { 5 | public static async getMetadataCount(projectDirectory: string, sourceDirectory: string): Promise { 6 | let metadataCount; 7 | try { 8 | let metadataFiles: string[] = globSync(`**/*-meta.xml`, { 9 | cwd: projectDirectory ? path.join(projectDirectory, sourceDirectory) : sourceDirectory, 10 | absolute: true, 11 | }); 12 | metadataCount = metadataFiles.length; 13 | } catch (error) { 14 | metadataCount = -1; 15 | } 16 | return metadataCount; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/core/src/package/components/PackageToComponent.ts: -------------------------------------------------------------------------------- 1 | import { ComponentSet } from '@salesforce/source-deploy-retrieve'; 2 | import Component from '../../dependency/Component'; 3 | 4 | 5 | export default class PackageToComponent { 6 | public constructor(private packageName:string,private packageDirectory:string) {} 7 | 8 | public generateComponents() { 9 | const components: Component[] = []; 10 | 11 | let componentSet = ComponentSet.fromSource(this.packageDirectory); 12 | 13 | let componentSetArray = componentSet.getSourceComponents().toArray(); 14 | 15 | for (const individualComponentFromComponentSet of componentSetArray) { 16 | const component: Component = { 17 | id: undefined, 18 | fullName: individualComponentFromComponentSet.fullName, 19 | type: individualComponentFromComponentSet.type.name, 20 | files: [individualComponentFromComponentSet.xml], 21 | package: this.packageName, 22 | }; 23 | components.push(component); 24 | } 25 | 26 | return components; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/core/src/package/deploymentCustomizers/DeploymentCustomizer.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from "@dxatscale/sfp-logger"; 2 | import { Connection } from "@salesforce/core"; 3 | import { ComponentSet } from "@salesforce/source-deploy-retrieve"; 4 | import { DeploymentOptions } from "../../deployers/DeploySourceToOrgImpl"; 5 | import SfpPackage from "../SfpPackage"; 6 | import SFPOrg from "../../org/SFPOrg"; 7 | import { DeploySourceResult } from "../../deployers/DeploymentExecutor"; 8 | 9 | export interface DeploymentContext 10 | { 11 | apiVersion: string; 12 | waitTime: string; 13 | } 14 | 15 | export interface DeploymentCustomizer 16 | { 17 | gatherComponentsToBeDeployed(sfpPackage: SfpPackage, componentSet:ComponentSet, conn: Connection, logger: Logger):Promise<{location:string, componentSet:ComponentSet}>; 18 | isEnabled(sfpPackage:SfpPackage, conn:Connection,logger:Logger):Promise; 19 | getDeploymentOptions( target_org: string, waitTime: string, apiVersion: string):Promise 20 | getName():string 21 | execute(sfpPackage: SfpPackage, 22 | componentSet: ComponentSet, 23 | sfpOrg:SFPOrg, 24 | logger: Logger, 25 | deploymentContext:DeploymentContext 26 | ):Promise 27 | } -------------------------------------------------------------------------------- /packages/core/src/package/deploymentCustomizers/PostDeployersRegistry.ts: -------------------------------------------------------------------------------- 1 | import { DeploymentCustomizer } from './DeploymentCustomizer'; 2 | import FHTEnabler from './FHTEnabler'; 3 | import FTEnabler from './FTEnabler'; 4 | 5 | 6 | export class PostDeployersRegistry { 7 | static getPostDeployers(): DeploymentCustomizer[] { 8 | let postDeployers: DeploymentCustomizer[] = []; 9 | 10 | //TODO: Make dynamic 11 | let fhtEnabler = new FHTEnabler(); 12 | let ftEnabler = new FTEnabler(); 13 | postDeployers.push(fhtEnabler); 14 | postDeployers.push(ftEnabler); 15 | 16 | return postDeployers; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/core/src/package/deploymentCustomizers/PreDeployersRegistry.ts: -------------------------------------------------------------------------------- 1 | import { DeploymentCustomizer } from './DeploymentCustomizer'; 2 | import PicklistEnabler from './PicklistEnabler'; 3 | 4 | 5 | export class PreDeployersRegistry { 6 | static getPreDeployers(): DeploymentCustomizer[] { 7 | let preDeployers: DeploymentCustomizer[] = []; 8 | 9 | let picklistEnabler = new PicklistEnabler(); 10 | preDeployers.push(picklistEnabler); 11 | 12 | return preDeployers; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/core/src/package/deploymentFilters/DeploymentFilter.ts: -------------------------------------------------------------------------------- 1 | import { ComponentSet } from "@salesforce/source-deploy-retrieve"; 2 | import { Logger } from "@dxatscale/sfp-logger"; 3 | import SFPOrg from "../../org/SFPOrg"; 4 | import { PackageType } from "../SfpPackage"; 5 | 6 | export interface DeploymentFilter 7 | { 8 | apply(org: SFPOrg, componentSet: ComponentSet,logger:Logger):Promise; 9 | isToApply(projectConfig: any,packageType:string): boolean; 10 | 11 | } 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/core/src/package/deploymentFilters/DeploymentFilterRegistry.ts: -------------------------------------------------------------------------------- 1 | import { DeploymentFilter } from './DeploymentFilter'; 2 | import EntitlementVersionFilter from './EntitlementVersionFilter'; 3 | 4 | 5 | 6 | 7 | export class DeploymentFilterRegistry { 8 | static getImplementations(): DeploymentFilter[] { 9 | let deploymentFilterImpls: DeploymentFilter[] = []; 10 | 11 | //TODO: Make dynamic 12 | let entitlementVersionFilter = new EntitlementVersionFilter(); 13 | deploymentFilterImpls.push(entitlementVersionFilter); 14 | 15 | 16 | return deploymentFilterImpls; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/core/src/package/packageInstallers/PackageInstallationResult.ts: -------------------------------------------------------------------------------- 1 | export type PackageInstallationResult = { 2 | result: PackageInstallationStatus; 3 | deploy_id?: string; 4 | message?: string; 5 | elapsedTime?:number; 6 | isPreScriptExecutionSuceeded?: boolean; 7 | isPostScriptExecutionSuceeeded?:boolean; 8 | numberOfComponentsDeployed?:number; 9 | }; 10 | 11 | export enum PackageInstallationStatus { 12 | Skipped, 13 | Succeeded, 14 | Failed, 15 | } 16 | -------------------------------------------------------------------------------- /packages/core/src/package/promote/PromoteUnlockedPackageImpl.ts: -------------------------------------------------------------------------------- 1 | import SFPLogger from '@dxatscale/sfp-logger'; 2 | import { SfProject } from '@salesforce/core'; 3 | import { PackageSaveResult, PackageVersion } from '@salesforce/packaging'; 4 | import SFPOrg from '../../org/SFPOrg'; 5 | 6 | export default class PromoteUnlockedPackageImpl { 7 | public constructor( 8 | private project_directory: string, 9 | private package_version_id: string, 10 | private devhub_alias: string 11 | ) {} 12 | 13 | public async promote(): Promise { 14 | let hubOrg = await SFPOrg.create({ aliasOrUsername: this.devhub_alias }); 15 | let project = await SfProject.resolve(this.project_directory); 16 | 17 | const packageVersion = new PackageVersion({ 18 | connection: hubOrg.getConnection(), 19 | project: project, 20 | idOrAlias: this.package_version_id, 21 | }); 22 | const packageVersionData = await packageVersion.getData(); 23 | 24 | let result: PackageSaveResult; 25 | try { 26 | result = await packageVersion.promote(); 27 | result.id = packageVersionData.SubscriberPackageVersionId; 28 | } catch (e) { 29 | if (e.message.includes('previously released')) { 30 | SFPLogger.log(`Package ${this.package_version_id} is already promoted, Ignoring`); 31 | } else throw e; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/core/src/package/propertyFetchers/AssignPermissionSetFetcher.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from '@dxatscale/sfp-logger'; 2 | import SfpPackage from '../SfpPackage'; 3 | import PropertyFetcher from './PropertyFetcher'; 4 | 5 | export default class AssignPermissionSetFetcher implements PropertyFetcher { 6 | public getSfpowerscriptsProperties(packageContents: SfpPackage, packageLogger?: Logger) { 7 | if (packageContents.packageDescriptor.assignPermSetsPreDeployment) { 8 | if (packageContents.packageDescriptor.assignPermSetsPreDeployment instanceof Array) { 9 | packageContents.assignPermSetsPreDeployment = 10 | packageContents.packageDescriptor.assignPermSetsPreDeployment; 11 | } else throw new Error("Property 'assignPermSetsPreDeployment' must be of type array"); 12 | } 13 | 14 | if (packageContents.packageDescriptor.assignPermSetsPostDeployment) { 15 | if (packageContents.packageDescriptor.assignPermSetsPostDeployment instanceof Array) { 16 | packageContents.assignPermSetsPostDeployment = 17 | packageContents.packageDescriptor.assignPermSetsPostDeployment; 18 | } else throw new Error("Property 'assignPermSetsPostDeployment' must be of type array"); 19 | } 20 | 21 | return packageContents; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/core/src/package/propertyFetchers/DestructiveManifestPathFetcher.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs-extra'; 2 | import SfpPackage from '../SfpPackage'; 3 | import PropertyFetcher from './PropertyFetcher'; 4 | import xml2json from '../../utils/xml2json'; 5 | import { Logger } from '@dxatscale/sfp-logger'; 6 | 7 | export default class DestructiveManifestPathFetcher implements PropertyFetcher { 8 | public async getSfpowerscriptsProperties(packageContents: SfpPackage, packageLogger?: Logger) { 9 | let destructiveChangesPath: string; 10 | 11 | if (packageContents.packageDescriptor === null || packageContents.packageDescriptor === undefined) { 12 | throw new Error('Project Config (sfdx-project.json) is null'); 13 | } 14 | 15 | if (packageContents.packageDescriptor['destructiveChangePath']) { 16 | destructiveChangesPath = packageContents.packageDescriptor['destructiveChangePath']; 17 | packageContents.destructiveChangesPath = destructiveChangesPath; 18 | } 19 | 20 | try { 21 | if (destructiveChangesPath != null) { 22 | packageContents.destructiveChanges = await xml2json(fs.readFileSync(destructiveChangesPath, 'utf8')); 23 | } 24 | } catch (error) { 25 | throw new Error('Unable to process destructive Manifest specified in the path or in the project manifest'); 26 | } 27 | return packageContents; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/core/src/package/propertyFetchers/PropertyFetcher.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from '@dxatscale/sfp-logger'; 2 | import SfpPackage from '../SfpPackage'; 3 | 4 | export default interface PropertyFetcher { 5 | /** 6 | * Retrieves property from packageDescriptor and adds its to SfpPackage by reference 7 | * @param packageContents 8 | * @param packageLogger 9 | */ 10 | getSfpowerscriptsProperties(packageContents: SfpPackage, packageLogger?: Logger); 11 | } 12 | -------------------------------------------------------------------------------- /packages/core/src/package/propertyFetchers/ReconcileProfilePropertyFetcher.ts: -------------------------------------------------------------------------------- 1 | import SfpPackage from '../SfpPackage'; 2 | import PropertyFetcher from './PropertyFetcher'; 3 | 4 | export default class ReconcilePropertyFetcher implements PropertyFetcher { 5 | getSfpowerscriptsProperties(packageContents: SfpPackage, packageLogger?: any) { 6 | if (packageContents.packageDescriptor.hasOwnProperty('reconcileProfiles')) { 7 | packageContents.reconcileProfiles = packageContents.packageDescriptor.reconcileProfiles; 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/core/src/package/version/Package2VersionInstaller.ts: -------------------------------------------------------------------------------- 1 | import { Logger, LoggerLevel } from '@dxatscale/sfp-logger'; 2 | 3 | export default class Package2VersionInstaller { 4 | public constructor( 5 | logger: Logger, 6 | logLevel: LoggerLevel, 7 | working_directory: string, 8 | private targetUserName: string, 9 | private packageId: string, 10 | private waitTime: string, 11 | private publishWaitTime?: string, 12 | private installationkey?: string, 13 | private securityType?: string, 14 | private upgradeType?: string, 15 | private apiVersion?: string, 16 | private apexCompile: string = 'package' 17 | ) {} 18 | 19 | public setInstallationKey(installationKey: string) { 20 | this.installationkey = installationKey; 21 | } 22 | 23 | 24 | 25 | 26 | } 27 | -------------------------------------------------------------------------------- /packages/core/src/package/version/PackageVersionUpdater.ts: -------------------------------------------------------------------------------- 1 | import SfpPackage from '../SfpPackage'; 2 | 3 | export default class PackageVersionUpdater { 4 | public constructor() {} 5 | 6 | public substituteBuildNumber(sfpPackage: SfpPackage, buildNumber: string):string { 7 | if (!sfpPackage.versionNumber) { 8 | throw new Error('The package doesnt have a version attribute, Please check your definition'); 9 | } else { 10 | let segments = sfpPackage.versionNumber.split('.'); 11 | let numberToBeAppended = parseInt(buildNumber); 12 | 13 | if (isNaN(numberToBeAppended)) throw new Error('BuildNumber should be a number'); 14 | else segments[3] = buildNumber; 15 | return `${segments[0]}.${segments[1]}.${segments[2]}.${segments[3]}`; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/core/src/permsets/AssignPermissionSets.ts: -------------------------------------------------------------------------------- 1 | import { Connection } from '@salesforce/core'; 2 | import { Logger } from '@dxatscale/sfp-logger'; 3 | import AssignPermissionSetsImpl from './AssignPermissionSetsImpl'; 4 | 5 | export default class AssignPermissionSets { 6 | static async applyPermsets(permsets: string[], conn: Connection, sourceDirectory: string, logger: Logger) { 7 | let assignPermissionSetsImpl: AssignPermissionSetsImpl = new AssignPermissionSetsImpl( 8 | conn, 9 | permsets, 10 | sourceDirectory, 11 | logger 12 | ); 13 | 14 | let results = await assignPermissionSetsImpl.exec(); 15 | if (results.failedAssignments.length > 0) throw new Error('Unable to assign permsets'); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/core/src/permsets/PermissionSetFetcher.ts: -------------------------------------------------------------------------------- 1 | import { Connection } from '@salesforce/core'; 2 | import QueryHelper from '../queryHelper/QueryHelper'; 3 | 4 | /* 5 | * Retrieve Permsets for a user from a target org 6 | */ 7 | export default class PermissionSetFetcher { 8 | constructor(private username: string, private conn: Connection) {} 9 | 10 | public async fetchAllPermsetAssignment() { 11 | const query = `SELECT Id, PermissionSet.Name, Assignee.Username FROM PermissionSetAssignment WHERE Assignee.Username = '${this.username}'`; 12 | 13 | return QueryHelper.query(query, this.conn, false); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/core/src/queryHelper/ChunkCollection.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | /** 4 | * Split values in SOQL WHERE clause into chunks to avoid exceeding max. URI length (16,000 chars) or max. WHERE clause length (4000 chars) 5 | * @param collection values in SOQL WHERE clause 6 | * @param chunkSize default is 4000 7 | * @param offset offset to account for keywords, fields, operators and literals in the query. Default is 1000 8 | */ 9 | export default function chunkCollection(collection: string[], chunkSize: number = 4000, offset: number = 1000): string[][] { 10 | const result: string[][] = []; 11 | chunkSize = chunkSize - offset; 12 | 13 | let chunk: string[] = []; 14 | let numberOfCharsInChunk: number = 0; 15 | for (const elem of collection) { 16 | if (elem.length + 2 > chunkSize) { 17 | throw new Error(`Single value cannot exceed chunk size limit of ${chunkSize}`); 18 | } 19 | 20 | const commasAndQuotes = 2*(chunk.length+1) + chunk.length; 21 | if (numberOfCharsInChunk + elem.length + commasAndQuotes <= chunkSize) { 22 | chunk.push(elem); 23 | numberOfCharsInChunk += elem.length; 24 | } else { 25 | result.push(chunk); 26 | 27 | // Create new chunk 28 | chunk = []; 29 | numberOfCharsInChunk = 0; 30 | chunk.push(elem); 31 | numberOfCharsInChunk += elem.length; 32 | } 33 | } 34 | 35 | result.push(chunk); 36 | 37 | return result; 38 | } -------------------------------------------------------------------------------- /packages/core/src/queryHelper/QueryHelper.ts: -------------------------------------------------------------------------------- 1 | import { Connection } from '@salesforce/core'; 2 | 3 | const retry = require('async-retry'); 4 | 5 | export default class QueryHelper { 6 | static async query(query: string, conn: Connection, isTooling: boolean): Promise { 7 | return retry( 8 | async (bail) => { 9 | let records; 10 | if (isTooling) records = (await conn.tooling.query(query)).records; 11 | else records = (await conn.query(query)).records; 12 | 13 | return records; 14 | }, 15 | { retries: 3, minTimeout: 2000 } 16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/core/src/scratchorg/ScratchOrg.ts: -------------------------------------------------------------------------------- 1 | export default interface ScratchOrg { 2 | failureMessage?: string; 3 | tag?: string; 4 | recordId?: string; 5 | orgId?: string; 6 | loginURL?: string; 7 | signupEmail?: string; 8 | username?: string; 9 | alias?: string; 10 | password?: string; 11 | isScriptExecuted?: boolean; 12 | expiryDate?: string; 13 | accessToken?: string; 14 | instanceURL?: string; 15 | status?: string; 16 | sfdxAuthUrl?: string; 17 | elapsedTime?:number 18 | } 19 | -------------------------------------------------------------------------------- /packages/core/src/scratchorg/pool/PoolBaseImpl.ts: -------------------------------------------------------------------------------- 1 | import { Org } from '@salesforce/core'; 2 | import { Result } from 'neverthrow'; 3 | import ScratchOrg from '../ScratchOrg'; 4 | import { PoolConfig } from './PoolConfig'; 5 | import { PoolError } from './PoolError'; 6 | import PreRequisiteCheck from './prequisitecheck/PreRequisiteCheck'; 7 | 8 | export abstract class PoolBaseImpl { 9 | protected hubOrg: Org; 10 | 11 | constructor(hubOrg: Org) { 12 | this.hubOrg = hubOrg; 13 | } 14 | 15 | public async execute(): Promise|void> { 16 | let prerequisiteCheck: PreRequisiteCheck = new PreRequisiteCheck(this.hubOrg); 17 | await prerequisiteCheck.checkForPrerequisites(); 18 | return this.onExec(); 19 | } 20 | 21 | protected abstract onExec(): Promise|void>; 22 | } 23 | -------------------------------------------------------------------------------- /packages/core/src/scratchorg/pool/PoolConfig.ts: -------------------------------------------------------------------------------- 1 | import ScratchOrg from '../ScratchOrg'; 2 | 3 | export interface PoolConfig { 4 | tag: string; 5 | maxAllocation: number; 6 | waitTime?: number; 7 | expiry?: number; 8 | batchSize?: number; 9 | configFilePath: string; 10 | releaseConfigFile?:string; 11 | succeedOnDeploymentErrors?: boolean; 12 | keys?: string; 13 | installAll: boolean; 14 | enableSourceTracking: boolean; 15 | relaxAllIPRanges?: boolean; 16 | ipRangesToBeRelaxed?: []; 17 | retryOnFailure?: boolean; 18 | fetchArtifacts: { 19 | artifactFetchScript?: string; 20 | npm?: { 21 | npmrcPath?: string; 22 | scope: string; 23 | }; 24 | }; 25 | disableSourcePackageOverride?:boolean; 26 | snapshotPool?:string; 27 | postDeploymentScriptPath: string; 28 | preDependencyInstallationScriptPath: string; 29 | enableVlocity?: boolean; 30 | min_allocation?: number; 31 | current_allocation?: number; 32 | to_allocate?: number; 33 | to_satisfy_min?: number; 34 | to_satisfy_max?: number; 35 | scratchOrgs?: ScratchOrg[]; 36 | failedToCreate?: number; 37 | maxRetryCount?:number; 38 | 39 | 40 | } 41 | -------------------------------------------------------------------------------- /packages/core/src/scratchorg/pool/PoolError.ts: -------------------------------------------------------------------------------- 1 | export interface PoolError { 2 | success: number; 3 | failed: number; 4 | errorCode: PoolErrorCodes; 5 | message?: string; 6 | } 7 | 8 | export enum PoolErrorCodes { 9 | Max_Capacity = 'MaxCapacity', 10 | No_Capacity = 'NoCapacity', 11 | PrerequisiteMissing = 'PrerequisitesMissing', 12 | UnableToProvisionAny = 'UnableToProvisionAny', 13 | } 14 | -------------------------------------------------------------------------------- /packages/core/src/scratchorg/pool/PoolJobExecutor.ts: -------------------------------------------------------------------------------- 1 | import { Org } from '@salesforce/core'; 2 | import { PoolConfig } from './PoolConfig'; 3 | import ScratchOrg from '../ScratchOrg'; 4 | import { Result } from 'neverthrow'; 5 | import * as fs from 'fs-extra'; 6 | import { EOL } from 'os'; 7 | import SFPLogger, { LoggerLevel } from '@dxatscale/sfp-logger'; 8 | 9 | export default abstract class PoolJobExecutor { 10 | protected logToFilePath: string; 11 | 12 | constructor(protected pool: PoolConfig) {} 13 | 14 | async execute( 15 | scratchOrg: ScratchOrg, 16 | hubOrg: Org, 17 | logLevel: LoggerLevel 18 | ): Promise> { 19 | this.logToFilePath = `.sfpowerscripts/prepare_logs/${scratchOrg.alias}.log`; 20 | //Create file logger 21 | fs.outputFileSync(this.logToFilePath, `sfpowerscripts--log${EOL}`); 22 | SFPLogger.log(`Preparation Log files for ${scratchOrg.username} written to ${this.logToFilePath}`); 23 | return this.executeJob(scratchOrg, hubOrg, this.logToFilePath, logLevel); 24 | } 25 | 26 | abstract executeJob( 27 | scratchOrg: ScratchOrg, 28 | hubOrg: Org, 29 | logToFilePath: string, 30 | logLevel: LoggerLevel 31 | ): Promise>; 32 | } 33 | 34 | export interface ScriptExecutionResult { 35 | scratchOrgUsername: string; 36 | } 37 | 38 | export interface JobError { 39 | message: string; 40 | scratchOrgUsername: string; 41 | } 42 | -------------------------------------------------------------------------------- /packages/core/src/scratchorg/pool/PoolOrgDeleteImpl.ts: -------------------------------------------------------------------------------- 1 | import { Org } from '@salesforce/core'; 2 | import { PoolBaseImpl } from './PoolBaseImpl'; 3 | import ScratchOrgInfoFetcher from './services/fetchers/ScratchOrgInfoFetcher'; 4 | import ScratchOrgOperator from '../ScratchOrgOperator'; 5 | 6 | export default class PoolOrgDeleteImpl extends PoolBaseImpl { 7 | username: string; 8 | 9 | public constructor(hubOrg: Org, username: string) { 10 | super(hubOrg); 11 | this.hubOrg = hubOrg; 12 | this.username = username; 13 | } 14 | 15 | protected async onExec(): Promise { 16 | try { 17 | let scratchOrgId = await new ScratchOrgInfoFetcher(this.hubOrg).getScratchOrgIdGivenUserName(this.username); 18 | await new ScratchOrgOperator(this.hubOrg).delete(scratchOrgId); 19 | } catch (err) { 20 | throw new Error( 21 | `Either the scratch org doesn't exist or you do not have the correct permissions, Failed with ` + 22 | err.message 23 | ); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/core/src/scratchorg/pool/prequisitecheck/IsValidSfdxAuthUrl.ts: -------------------------------------------------------------------------------- 1 | export default function isValidSfdxAuthUrl(sfdxAuthUrl: string): boolean { 2 | if (sfdxAuthUrl.match(/force:\/\/(?[a-zA-Z0-9._]+)@.+/)) { 3 | return true; 4 | } else { 5 | let match = sfdxAuthUrl.match( 6 | /force:\/\/(?[a-zA-Z0-9._=]+):(?[a-zA-Z0-9]*):(?[a-zA-Z0-9._=]+)@.+/ 7 | ); 8 | 9 | if (match !== null) { 10 | if (match.groups.refreshToken === 'undefined') { 11 | return false; 12 | } else { 13 | return true; 14 | } 15 | } else { 16 | return false; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/core/src/scratchorg/pool/services/fetchers/GetUserEmail.ts: -------------------------------------------------------------------------------- 1 | import { LoggerLevel, Org } from '@salesforce/core'; 2 | 3 | let retry = require('async-retry'); 4 | import SFPLogger from '@dxatscale/sfp-logger'; 5 | 6 | export async function getUserEmail(username: string, hubOrg: Org) { 7 | let hubConn = hubOrg.getConnection(); 8 | 9 | return retry( 10 | async (bail) => { 11 | if (!username) { 12 | bail(new Error('username cannot be null. provide a valid username')); 13 | return; 14 | } 15 | let query = `SELECT email FROM user WHERE username='${username}'`; 16 | 17 | SFPLogger.log('QUERY:' + query, LoggerLevel.TRACE); 18 | const results = (await hubConn.query(query)) as any; 19 | 20 | if (results.records.size < 1) { 21 | bail(new Error(`No user found with username ${username} in devhub.`)); 22 | return; 23 | } 24 | return results.records[0].Email; 25 | }, 26 | { retries: 3, minTimeout: 3000 } 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /packages/core/src/scratchorg/pool/services/fetchers/ScratchOrgLimitsFetcher.ts: -------------------------------------------------------------------------------- 1 | import { Org } from '@salesforce/core'; 2 | 3 | export default class ScratchOrgLimitsFetcher { 4 | constructor(private hubOrg: Org) {} 5 | 6 | public async getScratchOrgLimits(): Promise { 7 | let conn = this.hubOrg.getConnection(); 8 | let apiVersion = await conn.retrieveMaxApiVersion(); 9 | let query_uri = `${conn.instanceUrl}/services/data/v${apiVersion}/limits`; 10 | const result = await conn.request(query_uri); 11 | return result; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/core/src/scratchorg/pool/services/updaters/ScratchOrgInfoAssigner.ts: -------------------------------------------------------------------------------- 1 | import { LoggerLevel, Org } from '@salesforce/core'; 2 | let retry = require('async-retry'); 3 | import SFPLogger from '@dxatscale/sfp-logger'; 4 | import ScratchOrgInfoFetcher from '../fetchers/ScratchOrgInfoFetcher'; 5 | import ObjectCRUDHelper from '../../../../utils/ObjectCRUDHelper'; 6 | 7 | export default class ScratchOrgInfoAssigner { 8 | constructor(private hubOrg: Org) {} 9 | 10 | public async setScratchOrgInfo(soInfo: any): Promise { 11 | let hubConn = this.hubOrg.getConnection(); 12 | let result = await ObjectCRUDHelper.updateRecord(hubConn,'ScratchOrgInfo',soInfo); 13 | if(result) return true; 14 | } 15 | 16 | public async setScratchOrgStatus(username: string, status: 'Allocate' | 'InProgress' | 'Return'): Promise { 17 | let scratchOrgId = await new ScratchOrgInfoFetcher(this.hubOrg).getScratchOrgInfoIdGivenUserName(username); 18 | 19 | return this.setScratchOrgInfo({ 20 | Id: scratchOrgId, 21 | Allocation_status__c: status, 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/core/src/scriptExecutor/ScriptExecutorHelpers.ts: -------------------------------------------------------------------------------- 1 | import ExecuteCommand from '@dxatscale/sfdx-process-wrapper/lib/commandExecutor/ExecuteCommand'; 2 | import SFPLogger, { Logger, LoggerLevel } from '@dxatscale/sfp-logger'; 3 | import defaultShell from '../utils/DefaultShell'; 4 | 5 | export default class scriptExecutorHelpers { 6 | static async executeScript(logger: Logger, ...args: string[]) { 7 | let cmd: string; 8 | let argStr =args.join(' '); 9 | if (process.platform !== 'win32') { 10 | cmd = `${defaultShell()} -e ${argStr}`; 11 | } else { 12 | cmd = `cmd.exe /c ${argStr}`; 13 | } 14 | 15 | SFPLogger.log(`Executing command.. ${cmd}`,LoggerLevel.INFO,logger); 16 | let scriptExecutor: ExecuteCommand = new ExecuteCommand(logger, LoggerLevel.INFO, true); 17 | let result = await scriptExecutor.execCommand(cmd, null); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/core/src/sfdmuwrapper/SFDMURunImpl.ts: -------------------------------------------------------------------------------- 1 | import { SFDXCommand } from '@dxatscale/sfdx-process-wrapper/lib/SFDXCommand'; 2 | import { Logger, LoggerLevel } from '@dxatscale/sfp-logger'; 3 | 4 | export default class SFDMURunImpl extends SFDXCommand { 5 | public constructor( 6 | working_directory: string, 7 | target_org: string, 8 | private targetOrgDomain: string, 9 | private packageDirectory: string, 10 | logger: Logger, 11 | logLevel: LoggerLevel 12 | ) { 13 | super(target_org, working_directory, logger, logLevel); 14 | } 15 | 16 | getSFDXCommand(): string { 17 | return 'sf sfdmu run'; 18 | } 19 | getCommandName(): string { 20 | return 'sfdmu run'; 21 | } 22 | 23 | getGeneratedParams(): string { 24 | let command = `--path ${this.packageDirectory} -s csvfile -u ${this.target_org} --noprompt --canmodify ${this.targetOrgDomain}`; 25 | if (this.logLevel) command += ` --loglevel ${LoggerLevel[this.logLevel]}`; 26 | return command; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/core/src/stats/NativeMetricSender.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from '@dxatscale/sfp-logger'; 2 | 3 | export abstract class NativeMetricSender { 4 | constructor(protected logger: Logger) {} 5 | 6 | abstract initialize(apiHost: string, apiKey: string): void; 7 | 8 | abstract sendGaugeMetric(metric: string, value: number, tags: string[] | { [key: string]: string }): void; 9 | 10 | abstract sendCountMetric(metric: string, tags: string[] | { [key: string]: string }): void; 11 | 12 | protected transformTagsToStringArray(tags: { [key: string]: string } | string[]): string[] { 13 | if (tags != null && !Array.isArray(tags)) { 14 | let transformedTagArray: string[] = []; 15 | for (const [key, value] of Object.entries(tags)) { 16 | transformedTagArray.push(`${key}:${value}`); 17 | } 18 | return transformedTagArray; 19 | } 20 | return tags as string[]; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/core/src/utils/AliasList.ts: -------------------------------------------------------------------------------- 1 | import { StateAggregator } from '@salesforce/core'; 2 | 3 | 4 | export async function convertAliasToUsername(alias: string) { 5 | const stateAggregator = await StateAggregator.getInstance(); 6 | await stateAggregator.orgs.readAll(); 7 | return await stateAggregator.aliases.resolveUsername(alias) 8 | } 9 | 10 | export async function convertUsernameToAlias(username: string) { 11 | 12 | const stateAggregator = await StateAggregator.getInstance(); 13 | await stateAggregator.orgs.readAll(); 14 | return await stateAggregator.aliases.resolveAlias(username) 15 | 16 | } 17 | -------------------------------------------------------------------------------- /packages/core/src/utils/ChunkArray.ts: -------------------------------------------------------------------------------- 1 | export function chunkArray(perChunk: number, inputArray: any[]): Array { 2 | let chunks = [], 3 | i = 0, 4 | n = inputArray.length; 5 | 6 | while (i < n) { 7 | chunks.push(inputArray.slice(i, (i += perChunk))); 8 | } 9 | 10 | return chunks; 11 | } 12 | -------------------------------------------------------------------------------- /packages/core/src/utils/DefaultShell.ts: -------------------------------------------------------------------------------- 1 | const SFPOWERSCRIPTS_DEFAULT_SHELL = `sh`; 2 | 3 | export default function defaultShell(): string { 4 | return process.env.SFPOWERSCRIPTS_DEFAULT_SHELL 5 | ? process.env.SFPOWERSCRIPTS_DEFAULT_SHELL 6 | : SFPOWERSCRIPTS_DEFAULT_SHELL; 7 | } 8 | -------------------------------------------------------------------------------- /packages/core/src/utils/Delay.ts: -------------------------------------------------------------------------------- 1 | export async function delay(ms: number = 0) { 2 | return new Promise((resolve) => setTimeout(resolve, ms)); 3 | } 4 | -------------------------------------------------------------------------------- /packages/core/src/utils/GetFormattedTime.ts: -------------------------------------------------------------------------------- 1 | export default function getFormattedTime(milliseconds: number): string { 2 | let date = new Date(0); 3 | date.setMilliseconds(milliseconds); 4 | let timeString = date.toISOString().substr(11, 12); 5 | return timeString; 6 | } 7 | -------------------------------------------------------------------------------- /packages/core/src/utils/ObjectCRUDHelper.ts: -------------------------------------------------------------------------------- 1 | import { Connection } from '@salesforce/core'; 2 | import { Record, SaveResult } from 'jsforce'; 3 | import { isArray } from 'lodash'; 4 | 5 | const retry = require('async-retry'); 6 | 7 | export default class ObjectCRUDHelper { 8 | static async updateRecord(conn: Connection, sObject: string, record: Record): Promise { 9 | return retry( 10 | async (bail) => { 11 | let result = await conn.update(sObject, record); 12 | if (isArray(result)) { 13 | let isAllRecordsSucceeded = true; 14 | for (const individualResult of result as SaveResult[]) { 15 | if (!individualResult.success) { 16 | isAllRecordsSucceeded = false; 17 | } 18 | } 19 | if (isAllRecordsSucceeded) return 'All records updated'; 20 | else throw new Error('Some records have been failed to update'); 21 | } else if ((result as SaveResult).success) return (result as SaveResult).id; 22 | else bail(); 23 | }, 24 | { retries: 3, minTimeout: 2000 } 25 | ); 26 | } 27 | 28 | static async createRecord(conn: Connection, sObject: string, record: Record): Promise { 29 | return retry( 30 | async (bail) => { 31 | let result = await conn.create(sObject, record); 32 | if (result.success) return result.id; 33 | else bail(); 34 | }, 35 | { retries: 3, minTimeout: 2000 } 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/core/src/utils/OnExit.ts: -------------------------------------------------------------------------------- 1 | import { ChildProcess } from 'child_process'; 2 | 3 | export async function onExit(childProcess: ChildProcess, message?: string): Promise<{}> { 4 | return new Promise((resolve, reject) => { 5 | childProcess.once('close', (code: number, signal: string) => { 6 | if (code === 0 || (code === null && signal === 'SIGTERM')) { 7 | resolve(undefined); 8 | } else { 9 | reject(new Error(message ? message : `Exit with error code ${code}`)); 10 | } 11 | }); 12 | 13 | childProcess.once('error', (err: Error) => { 14 | reject(new Error(message ? message : err.message)); 15 | }); 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /packages/core/src/utils/RandomId.ts: -------------------------------------------------------------------------------- 1 | export function makeRandomId(length): string { 2 | let result = ''; 3 | const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 4 | const charactersLength = characters.length; 5 | for (let i = 0; i < length; i++) { 6 | result += characters.charAt(Math.floor(Math.random() * charactersLength)); 7 | } 8 | return result; 9 | } 10 | -------------------------------------------------------------------------------- /packages/core/src/utils/VersionNumberConverter.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Converts build-number dot delimeter to hyphen 3 | * If dot delimeter does not exist, returns input 4 | * @param version 5 | */ 6 | export default function convertBuildNumDotDelimToHyphen(version: string) { 7 | let convertedVersion = version; 8 | 9 | let indexOfBuildNumDelimiter = getIndexOfBuildNumDelimeter(version); 10 | if (version[indexOfBuildNumDelimiter] === '.') { 11 | convertedVersion = 12 | version.substring(0, indexOfBuildNumDelimiter) + '-' + version.substring(indexOfBuildNumDelimiter + 1); 13 | } 14 | return convertedVersion; 15 | } 16 | 17 | /** 18 | * Get the index of the build-number delimeter 19 | * Returns null if unable to find index of delimeter 20 | * @param version 21 | */ 22 | function getIndexOfBuildNumDelimeter(version: string) { 23 | let numOfDelimetersTraversed: number = 0; 24 | for (let i = 0; i < version.length; i++) { 25 | if (!Number.isInteger(parseInt(version[i], 10))) { 26 | numOfDelimetersTraversed++; 27 | } 28 | if (numOfDelimetersTraversed === 3) { 29 | return i; 30 | } 31 | } 32 | return null; 33 | } 34 | -------------------------------------------------------------------------------- /packages/core/src/utils/extractDomainFromUrl.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Extracts domain name from full URL string 3 | * @param url 4 | * @returns 5 | */ 6 | export default function extractDomainFromUrl(url: string): string { 7 | if (!url) return url; 8 | const matches = url.match(/^https?\:\/\/([^\/?#]+)(?:[\/?#]|$)/i); 9 | return matches && matches[1]; 10 | } 11 | -------------------------------------------------------------------------------- /packages/core/src/utils/xml2json.ts: -------------------------------------------------------------------------------- 1 | const xmlParser = require('xml2js').Parser({ explicitArray: false }); 2 | 3 | export default function xml2json(xml) { 4 | return new Promise((resolve, reject) => { 5 | xmlParser.parseString(xml, function (err, json) { 6 | if (err) reject(err); 7 | else resolve(json); 8 | }); 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /packages/core/src/vlocitywrapper/VlocityInitialInstall.ts: -------------------------------------------------------------------------------- 1 | import { SFDXCommand } from '@dxatscale/sfdx-process-wrapper/lib/SFDXCommand'; 2 | import { Logger, LoggerLevel } from '@dxatscale/sfp-logger'; 3 | 4 | export default class VlocityInitialInstall extends SFDXCommand { 5 | public constructor(project_directory: string, target_org: string, logger: Logger, logLevel: LoggerLevel) { 6 | super(target_org, project_directory, logger, logLevel); 7 | } 8 | 9 | getSFDXCommand(): string { 10 | return 'vlocity'; 11 | } 12 | getCommandName(): string { 13 | return 'vlocity:packUpdateSettings'; 14 | } 15 | 16 | getGeneratedParams(): string { 17 | let command = `-sfdx.username ${this.target_org} --nojob installVlocityInitial`; 18 | if (this.logLevel) command += ` -verbose`; 19 | return command; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/core/src/vlocitywrapper/VlocityPackDeployImpl.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { SFDXCommand } from '@dxatscale/sfdx-process-wrapper/lib/SFDXCommand'; 3 | import { Logger, LoggerLevel } from '@dxatscale/sfp-logger'; 4 | 5 | export default class VlocityPackDeployImpl extends SFDXCommand { 6 | public constructor( 7 | project_directory: string, 8 | target_org: string, 9 | private packageDirectory: string, 10 | logger: Logger, 11 | logLevel: LoggerLevel 12 | ) { 13 | super(target_org, project_directory, logger, logLevel); 14 | } 15 | 16 | getSFDXCommand(): string { 17 | return 'vlocity'; 18 | } 19 | getCommandName(): string { 20 | return 'vlocity:packDeploy'; 21 | } 22 | 23 | getGeneratedParams(): string { 24 | let command = `-sfdx.username ${this.target_org} -job ${path.join( 25 | this.packageDirectory, 26 | 'VlocityComponents.yaml' 27 | )} packDeploy`; 28 | if (this.logLevel) command += ` -verbose`; 29 | return command; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/core/src/vlocitywrapper/VlocityPackUpdateSettings.ts: -------------------------------------------------------------------------------- 1 | import { SFDXCommand } from '@dxatscale/sfdx-process-wrapper/lib/SFDXCommand'; 2 | import { Logger, LoggerLevel } from '@dxatscale/sfp-logger'; 3 | 4 | export default class VlocityPackUpdateSettings extends SFDXCommand { 5 | public constructor(project_directory: string, target_org: string, logger: Logger, logLevel: LoggerLevel) { 6 | super(target_org, project_directory, logger, logLevel); 7 | } 8 | 9 | getSFDXCommand(): string { 10 | return 'vlocity'; 11 | } 12 | getCommandName(): string { 13 | return 'vlocity:packUpdateSettings'; 14 | } 15 | 16 | getGeneratedParams(): string { 17 | let command = `-sfdx.username ${this.target_org} --nojob packUpdateSettings`; 18 | if (this.logLevel) command += ` -verbose`; 19 | return command; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/core/src/vlocitywrapper/VlocityRefreshBase.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { SFDXCommand } from '@dxatscale/sfdx-process-wrapper/lib/SFDXCommand'; 3 | import { Logger, LoggerLevel } from '@dxatscale/sfp-logger'; 4 | 5 | export default class VlocityRefreshBase extends SFDXCommand { 6 | public constructor( 7 | project_directory: string, 8 | target_org: string, 9 | private packageDirectory: string, 10 | logger: Logger, 11 | logLevel: LoggerLevel 12 | ) { 13 | super(target_org, project_directory, logger, logLevel); 14 | } 15 | 16 | getSFDXCommand(): string { 17 | return 'vlocity'; 18 | } 19 | getCommandName(): string { 20 | return 'vlocity:refreshVlocityBase'; 21 | } 22 | 23 | getGeneratedParams(): string { 24 | let command = `-sfdx.username ${this.target_org} -job ${path.join( 25 | this.packageDirectory, 26 | 'VlocityComponents.yaml' 27 | )} refreshVlocityBase`; 28 | if (this.logLevel) command += ` -verbose`; 29 | return command; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/core/tests/package/packageMerger/artifacts1/core2_sfpowerscripts_artifact_1.0.4-1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dxatscale/sfpowerscripts/e1b48d7c9e599a085d5eabd70d382fbdd8dabb13/packages/core/tests/package/packageMerger/artifacts1/core2_sfpowerscripts_artifact_1.0.4-1.zip -------------------------------------------------------------------------------- /packages/core/tests/package/packageMerger/artifacts1/feature-mgmt3_sfpowerscripts_artifact_1.0.6-1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dxatscale/sfpowerscripts/e1b48d7c9e599a085d5eabd70d382fbdd8dabb13/packages/core/tests/package/packageMerger/artifacts1/feature-mgmt3_sfpowerscripts_artifact_1.0.6-1.zip -------------------------------------------------------------------------------- /packages/core/tests/permsets/PermissionSetGroupUpdateAwaiter.test.ts: -------------------------------------------------------------------------------- 1 | import { MockTestOrgData, TestContext } from '@salesforce/core/lib/testSetup'; 2 | import { AuthInfo, Connection, OrgConfigProperties } from '@salesforce/core'; 3 | import { AnyJson } from '@salesforce/ts-types'; 4 | const $$ = new TestContext(); 5 | import PermissionSetGroupUpdateAwaiter from '../../src/permsets/PermissionSetGroupUpdateAwaiter'; 6 | import { expect } from '@jest/globals'; 7 | 8 | describe('Await till permissionsets groups are updated', () => { 9 | it('should return if all permsets groups are updated', async () => { 10 | const testData = new MockTestOrgData(); 11 | 12 | await $$.stubConfig({ [OrgConfigProperties.TARGET_ORG]: testData.username }); 13 | await $$.stubAuths(testData); 14 | $$.setConfigStubContents('AuthInfoConfig', { 15 | contents: await testData.getConfig(), 16 | }); 17 | 18 | let records: AnyJson = { 19 | records: [], 20 | }; 21 | $$.fakeConnectionRequest = (request: AnyJson): Promise => { 22 | return Promise.resolve(records); 23 | }; 24 | 25 | const connection: Connection = await Connection.create({ 26 | authInfo: await AuthInfo.create({ username: testData.username }), 27 | }); 28 | 29 | let permissionSetGroupUpdateAwaiter: PermissionSetGroupUpdateAwaiter = new PermissionSetGroupUpdateAwaiter( 30 | connection, 31 | null 32 | ); 33 | await expect(permissionSetGroupUpdateAwaiter.waitTillAllPermissionSetGroupIsUpdated()).resolves.toBeUndefined(); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /packages/core/tests/queryHelper/ChunkCollection.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from '@jest/globals'; 2 | import chunkCollection from '../../src/queryHelper/ChunkCollection'; 3 | 4 | describe('Given a collection', () => { 5 | 6 | it('should return a single chunk for a collection less than 3000 chars', () => { 7 | const collection = ["ApexClassA", "ApexClassB", "ApexClassC"]; 8 | const result = chunkCollection(collection); 9 | expect(result.length).toBe(1); 10 | expect(result).toEqual([ 11 | ["ApexClassA", "ApexClassB", "ApexClassC"] 12 | ]); 13 | }); 14 | 15 | 16 | it('should return N chunks for a collection exceeding the chunk size', () => { 17 | const collection = ["ApexClassA", "ApexClassB", "ApexClassC", "ApexClassD"]; 18 | const result = chunkCollection(collection, 1050, 1000); 19 | expect(result.length).toBe(2); 20 | expect(result).toEqual([ 21 | ["ApexClassA", "ApexClassB", "ApexClassC"], 22 | ["ApexClassD"] 23 | ]); 24 | }); 25 | 26 | it('should throw an error if single element in collection exceeds chunk size', () => { 27 | const collection = ["ApexClassWithAnExceedinglyLongNameGreaterThanTheChunkSize"]; 28 | expect(() => {chunkCollection(collection, 1050, 1000)}).toThrow(); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /packages/core/tests/utils/ChunkArray.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from '@jest/globals'; 2 | import { chunkArray } from '../../src/utils/ChunkArray'; 3 | 4 | describe('Given an input array', () => { 5 | const input = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; 6 | 7 | it('should chunk for even chunk size', () => { 8 | const result = chunkArray(2, input); 9 | expect(result.length).toBe(5); 10 | expect(result).toEqual([ 11 | [1, 2], 12 | [3, 4], 13 | [5, 6], 14 | [7, 8], 15 | [9, 10], 16 | ]); 17 | }); 18 | 19 | it('should chunk for odd chunk size', () => { 20 | const result = chunkArray(3, input); 21 | expect(result.length).toBe(4); 22 | expect(result).toEqual([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/core/tests/utils/FileSystem.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from '@jest/globals'; 2 | import FileSystem from '../../src/utils/FileSystem'; 3 | const path = require('path'); 4 | 5 | describe('Given a search directory', () => { 6 | it('should return nested files', () => { 7 | const resourcesDir = path.join(__dirname, 'resources'); 8 | let files = FileSystem.readdirRecursive(path.join(resourcesDir, 'a'), false, false); 9 | expect(files).toEqual(expectedFiles); 10 | 11 | files = FileSystem.readdirRecursive(path.join(resourcesDir, 'a'), true, false); 12 | expect(files).toEqual(expectedFilesIncludingDirs); 13 | 14 | files = FileSystem.readdirRecursive(path.join(resourcesDir, 'a'), false, true); 15 | expect(files).toEqual(expectedFiles.map((elem) => path.join(resourcesDir, 'a', elem))); 16 | 17 | files = FileSystem.readdirRecursive(path.join(resourcesDir, 'a'), true, true); 18 | expect(files).toEqual(expectedFilesIncludingDirs.map((elem) => path.join(resourcesDir, 'a', elem))); 19 | }); 20 | }); 21 | 22 | const expectedFiles = ['b/b1.file', 'b/c/c1.file', 'b/c/c2.file', 'b/d/d1.file', 'b/d/x/x1.file', 'b/e/e1.file']; 23 | const expectedFilesIncludingDirs = [ 24 | 'b', 25 | 'b/b1.file', 26 | 'b/c', 27 | 'b/c/c1.file', 28 | 'b/c/c2.file', 29 | 'b/d', 30 | 'b/d/d1.file', 31 | 'b/d/x', 32 | 'b/d/x/x1.file', 33 | 'b/e', 34 | 'b/e/e1.file', 35 | ]; 36 | -------------------------------------------------------------------------------- /packages/core/tests/utils/extractDomainFromUrl.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from '@jest/globals'; 2 | import extractDomainFromUrl from '../../src/utils/extractDomainFromUrl'; 3 | 4 | describe('Given a URL', () => { 5 | it('should extract the domain name for https', () => { 6 | expect(extractDomainFromUrl('https://force-power-8147.cs115.my.salesforce.com')).toBe( 7 | 'force-power-8147.cs115.my.salesforce.com' 8 | ); 9 | }); 10 | 11 | it('should extract the domain name for http', () => { 12 | expect(extractDomainFromUrl('https://force-power-8147.cs115.my.salesforce.com')).toBe( 13 | 'force-power-8147.cs115.my.salesforce.com' 14 | ); 15 | }); 16 | 17 | it('should extract only the domain name', () => { 18 | expect( 19 | extractDomainFromUrl( 20 | 'https://company.lightning.force.com/lightning/o/Account/list?filterName=00B4Y000000VyMDUA0' 21 | ) 22 | ).toBe('company.lightning.force.com'); 23 | }); 24 | 25 | it('should return null for protocol other than http/s', () => { 26 | expect(extractDomainFromUrl('ftp://ftp.example.com/files/fileA')).toBe(null); 27 | }); 28 | 29 | it('should return input for falsy values', () => { 30 | expect(extractDomainFromUrl('')).toBe(''); 31 | expect(extractDomainFromUrl(undefined)).toBe(undefined); 32 | expect(extractDomainFromUrl(null)).toBe(null); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /packages/core/tests/utils/resources/a/b/b1.file: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dxatscale/sfpowerscripts/e1b48d7c9e599a085d5eabd70d382fbdd8dabb13/packages/core/tests/utils/resources/a/b/b1.file -------------------------------------------------------------------------------- /packages/core/tests/utils/resources/a/b/c/c1.file: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dxatscale/sfpowerscripts/e1b48d7c9e599a085d5eabd70d382fbdd8dabb13/packages/core/tests/utils/resources/a/b/c/c1.file -------------------------------------------------------------------------------- /packages/core/tests/utils/resources/a/b/c/c2.file: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dxatscale/sfpowerscripts/e1b48d7c9e599a085d5eabd70d382fbdd8dabb13/packages/core/tests/utils/resources/a/b/c/c2.file -------------------------------------------------------------------------------- /packages/core/tests/utils/resources/a/b/d/d1.file: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dxatscale/sfpowerscripts/e1b48d7c9e599a085d5eabd70d382fbdd8dabb13/packages/core/tests/utils/resources/a/b/d/d1.file -------------------------------------------------------------------------------- /packages/core/tests/utils/resources/a/b/d/x/x1.file: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dxatscale/sfpowerscripts/e1b48d7c9e599a085d5eabd70d382fbdd8dabb13/packages/core/tests/utils/resources/a/b/d/x/x1.file -------------------------------------------------------------------------------- /packages/core/tests/utils/resources/a/b/e/e1.file: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dxatscale/sfpowerscripts/e1b48d7c9e599a085d5eabd70d382fbdd8dabb13/packages/core/tests/utils/resources/a/b/e/e1.file -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "experimentalDecorators": true, 5 | "outDir": "./lib", 6 | "rootDir": "./src", 7 | "skipLibCheck": true, 8 | "declarationMap": true, 9 | "baseUrl": "/src", 10 | "paths": {} 11 | }, 12 | "exclude": ["node_modules", "dist", "tests", "lib"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/forcemula/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | 4 | Original work Copyright (c) 2022 Pablo Gonzalez 5 | Modified work Copyright (c) DX@Scale 6 | 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | -------------------------------------------------------------------------------- /packages/forcemula/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dxatscale/sfpowerscripts/e1b48d7c9e599a085d5eabd70d382fbdd8dabb13/packages/forcemula/img/logo.png -------------------------------------------------------------------------------- /packages/forcemula/lib/MetadataTypes.js: -------------------------------------------------------------------------------- 1 | module.exports = class MetadataType { 2 | 3 | static CUSTOM_FIELD = new MetadataType("customFields") 4 | static STANDARD_FIELD = new MetadataType("standardFields") 5 | static CUSTOM_OBJECT = new MetadataType("customObjects") 6 | static STANDARD_OBJECT = new MetadataType("standardObjects") 7 | static CUSTOM_LABEL = new MetadataType("customLabels") 8 | static CUSTOM_SETTING = new MetadataType("customSettings") 9 | static CUSTOM_METADATA_TYPE_RECORD = new MetadataType("customMetadataTypeRecords") 10 | static CUSTOM_METADATA_TYPE = new MetadataType("customMetadataTypes") 11 | static UNKNOWN_RELATIONSHIP = new MetadataType("unknownRelationships") 12 | 13 | constructor(name) { 14 | this.name = name 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /packages/forcemula/lib/utils.js: -------------------------------------------------------------------------------- 1 | 2 | let $ = value => value.toUpperCase(); 3 | let parts = value => value.split('.'); 4 | let getObject = value => parts(value)[0]; 5 | let getField = value => parts(value)[1]; 6 | let removeWhiteSpace = value => value.replace(/\s/g,''); 7 | 8 | 9 | module.exports = {$,parts,getObject,getField,removeWhiteSpace} -------------------------------------------------------------------------------- /packages/forcemula/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dxatscale/forcemula", 3 | "version": "1.0.0", 4 | "description": "Extract fields from Salesforce formulas", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "start": "node src/index.js", 8 | "test": "jest --silent=false ", 9 | "watch": "jest --silent=false --verbose --detectOpenHandles --coverage --watchAll", 10 | "test:coverage": "jest --silent=false --verbose --detectOpenHandles --coverage" 11 | }, 12 | "author": "dxatscale", 13 | "license": "MIT", 14 | "devDependencies": { 15 | "jest": "^29.6.1" 16 | }, 17 | "jest": { 18 | "coverageReporters": [ 19 | "json-summary", 20 | "text", 21 | "lcov" 22 | ] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/forcemula/tests/utils.test.js: -------------------------------------------------------------------------------- 1 | let _ = require('../lib/utils'); 2 | 3 | let fieldName = 'Opportunity.StageName'; 4 | let fieldNameParts = ['Opportunity','StageName']; 5 | 6 | test('White space should be removed',() =>{ 7 | expect(_.removeWhiteSpace(' there is space ')).toBe('thereisspace'); 8 | }) 9 | 10 | test('Word should be converted to upper case',() =>{ 11 | expect(_.$('hi')).toBe('HI'); 12 | }) 13 | 14 | test('Parts should return the parts based on a dot delimeter', () => { 15 | expect(_.parts(fieldName)).toEqual(expect.arrayContaining(fieldNameParts)); 16 | }) 17 | 18 | test('Get object should return the object name', () => { 19 | expect(_.getObject(fieldName)).toEqual(fieldNameParts[0]); 20 | }) 21 | 22 | test('Get field should return the field name', () => { 23 | expect(_.getField(fieldName)).toEqual(fieldNameParts[1]); 24 | }) -------------------------------------------------------------------------------- /packages/sfdx-process-wrapper/README.md: -------------------------------------------------------------------------------- 1 | # `sfdx-process-wrapper` 2 | 3 | > A thin wrapper -------------------------------------------------------------------------------- /packages/sfdx-process-wrapper/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dxatscale/sfdx-process-wrapper", 3 | "version": "1.0.2", 4 | "description": "Wraps around SFDXCommand to exeute it using a subprocess", 5 | "author": "dxatscale", 6 | "homepage": "https://github.com/dxatscale/sfpowerscripts#readme", 7 | "license": "MIT", 8 | "main": "lib/index", 9 | "types": "lib/index", 10 | "directories": { 11 | "lib": "lib" 12 | }, 13 | "dependencies": { 14 | "@dxatscale/sfp-logger": "^2.1.2", 15 | "fs-extra": "^9.1.0" 16 | }, 17 | "devDependencies": { 18 | "@types/fs-extra": "^9.0.11", 19 | "@types/node": "^10", 20 | "typescript": "^5" 21 | }, 22 | "files": [ 23 | "lib" 24 | ], 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/dxatscale/sfpowerscripts.git" 28 | }, 29 | "publishConfig": { 30 | "access": "public" 31 | }, 32 | "scripts": { 33 | "build": "pnpm run clean && pnpm run compile", 34 | "clean": "rimraf ./lib && rimraf tsconfig.tsbuildinfo", 35 | "compile": "tsc -b tsconfig.json" 36 | }, 37 | "bugs": { 38 | "url": "https://github.com/dxatscale/sfpowerscripts/issues" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/sfdx-process-wrapper/src/SFDXCommand.ts: -------------------------------------------------------------------------------- 1 | import ExecuteCommand from './commandExecutor/ExecuteCommand'; 2 | import SFPLogger, { Logger, LoggerLevel } from '@dxatscale/sfp-logger'; 3 | 4 | export abstract class SFDXCommand { 5 | public constructor( 6 | protected target_org: string, 7 | protected project_directory: string, 8 | protected logger?: Logger, 9 | protected logLevel?: LoggerLevel 10 | ) {} 11 | 12 | public async exec(quiet:boolean = true, timeout: number = 0, showProgress: boolean = false): Promise { 13 | let command = this.getSFDXCommand(); 14 | //add log level to error 15 | command += ' --loglevel=ERROR'; 16 | 17 | if (quiet) command += ` --json`; 18 | command += ' ' + this.getGeneratedParams(); 19 | 20 | SFPLogger.log('Generated Command:' + command, LoggerLevel.TRACE, this.logger); 21 | let executor: ExecuteCommand = new ExecuteCommand(this.logger, this.logLevel, showProgress); 22 | //CLI writes errors to Output Stream during errors in JSON Mode, so if quiet is true, use swap output for error 23 | let output = await executor.execCommand(command, this.project_directory, timeout, quiet); 24 | if (quiet) { 25 | return JSON.parse(output).result; 26 | } 27 | return output; 28 | } 29 | 30 | public getGeneratedSFDXCommandWithParams() { 31 | let command = this.getSFDXCommand(); 32 | command += ' ' + this.getGeneratedParams(); 33 | return command; 34 | } 35 | 36 | abstract getSFDXCommand(): string; 37 | abstract getCommandName(): string; 38 | abstract getGeneratedParams(): string; 39 | } 40 | -------------------------------------------------------------------------------- /packages/sfdx-process-wrapper/src/commandExecutor/DefaultProcessOptions.ts: -------------------------------------------------------------------------------- 1 | import { ExecOptions } from "child_process"; 2 | 3 | 4 | const MAX_BUFFER_DEFAULT: number = 1024 * 1024 * 5; // 5MB 5 | 6 | export default function defaultProcessOptions(): ExecOptions { 7 | return { 8 | maxBuffer: process.env.SFPOWERSCRIPTS_PROCESS_MAX_BUFFER 9 | ? Number.parseInt(process.env.SFPOWERSCRIPTS_PROCESS_MAX_BUFFER) 10 | : MAX_BUFFER_DEFAULT, 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /packages/sfdx-process-wrapper/src/commandExecutor/SpawnCommand.ts: -------------------------------------------------------------------------------- 1 | import child_process = require('child_process'); 2 | import * as fs from 'fs-extra'; 3 | 4 | export default class SpawnCommand { 5 | public execCommand(command: string, workingdirectory: string, args: string[], fileToLog: string): Promise { 6 | return new Promise((resolve, reject) => { 7 | try { 8 | let childProcess; 9 | 10 | childProcess = child_process.spawn(command, args, { 11 | cwd: workingdirectory, 12 | }); 13 | 14 | // collect data written to STDOUT into a string 15 | childProcess.stdout.on('data', (data) => { 16 | fs.appendFileSync(fileToLog, data); 17 | }); 18 | 19 | // collect data written to STDERR into a string 20 | childProcess.stderr.on('data', (data) => { 21 | fs.appendFileSync(fileToLog, data); 22 | }); 23 | 24 | childProcess.once('close', (code: number, signal: string) => { 25 | if (code === 0 || (code === null && signal === 'SIGTERM')) { 26 | resolve(fileToLog); 27 | } else { 28 | reject(fileToLog); 29 | } 30 | }); 31 | 32 | childProcess.once('error', (err: Error) => { 33 | reject(fileToLog); 34 | }); 35 | } catch (error) { 36 | reject(error); 37 | } 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/sfdx-process-wrapper/src/index.ts: -------------------------------------------------------------------------------- 1 | export default {}; 2 | -------------------------------------------------------------------------------- /packages/sfdx-process-wrapper/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "experimentalDecorators": true, 5 | "outDir": "./lib", 6 | "rootDir": "./src", 7 | "skipLibCheck": true, 8 | "declarationMap": true 9 | }, 10 | "exclude": ["node_modules", "dist", "tests"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/sfplogger/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dxatscale/sfp-logger", 3 | "version": "2.1.2", 4 | "description": "sfp logger library", 5 | "main": "lib/SFPLogger.js", 6 | "types": "lib/SFPLogger.d.ts", 7 | "files": [ 8 | "lib" 9 | ], 10 | "scripts": { 11 | "build": "pnpm run clean && pnpm run compile", 12 | "clean": "rimraf ./lib && rimraf tsconfig.tsbuildinfo", 13 | "compile": "tsc -b tsconfig.json" 14 | }, 15 | "dependencies": { 16 | "chalk": "^4.1.2", 17 | "fs-extra": "^9.1.0", 18 | "strip-ansi": "^6.0.0" 19 | }, 20 | "devDependencies": { 21 | "@types/node": "^14.14.7", 22 | "typescript": "^5" 23 | }, 24 | "engines": { 25 | "node": ">=8.0.0" 26 | }, 27 | "repository": { 28 | "type": "git", 29 | "url": "git+https://github.com/dxatscale/sfpowerscripts.git" 30 | }, 31 | "author": "dxatscale", 32 | "license": "MIT", 33 | "bugs": { 34 | "url": "https://github.com/dxatscale/sfpowerscripts/issues" 35 | }, 36 | "homepage": "https://github.com/dxatscale/sfpowerscripts/tree/main/packages/sfplogger#readme", 37 | "keywords": [ 38 | "logger", 39 | "sfp", 40 | "dxatscale" 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /packages/sfplogger/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "composite": true, 6 | "paths": { "*": ["types/*"] }, 7 | "outDir": "./lib", 8 | "rootDir": "./src", 9 | "inlineSourceMap": true, 10 | "declaration": true, 11 | "noImplicitAny": false, 12 | "esModuleInterop": true, 13 | "module": "commonjs", 14 | "moduleResolution": "node", 15 | "target": "es6", 16 | "allowJs": true, 17 | "lib": ["es6"], 18 | "resolveJsonModule": true, 19 | "skipLibCheck": true, 20 | "declarationMap": true 21 | }, 22 | "exclude": ["node_modules", "lib"], 23 | "include": ["./src/**/*"] 24 | } -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/.babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | plugins: [ 4 | '@babel/plugin-proposal-optional-chaining', 5 | '@babel/plugin-proposal-nullish-coalescing-operator' 6 | ], 7 | }; -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # [20.30.0](https://github.com/dxatscale/sfpowerscripts/compare/@dxatscale/sfpowerscripts@20.29.0...@dxatscale/sfpowerscripts@20.30.0) (2023-03-22) 7 | 8 | 9 | ### Features 10 | 11 | * **publish:** Add new flag to delete Git tags by age and limit ([#1275](https://github.com/dxatscale/sfpowerscripts/issues/1275)) ([aae62d6](https://github.com/dxatscale/sfpowerscripts/commit/aae62d6d3e7eb390dddcf2ca46b99b44ca4cc933)) 12 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/bin/run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const oclif = require('@oclif/core') 4 | 5 | oclif.run().then(require('@oclif/core/flush')).catch(require('@oclif/core/handle')) -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/bin/run.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | node "%~dp0\run" %* 4 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest/presets/js-with-babel', 3 | testEnvironment: 'node', 4 | restoreMocks: true, 5 | clearMocks: true, 6 | resetMocks: true, 7 | transform: { 8 | '^.+\\.[t]sx?$': [ 9 | 'ts-jest', 10 | { 11 | tsconfig: 'tsconfig.json', 12 | babelConfig: true, 13 | }, 14 | ] 15 | }, 16 | transformIgnorePatterns: ['/node_modules/(?!@salesforce/source-deploy-retrieve)(.*)'], 17 | }; 18 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/messages/analyze.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandDescription": "Runs static analysis on the code/config based on configurable rules", 3 | "devhubAliasFlagDescription": "Provide the alias of the devhub previously authenticated, default value is HubOrg if using the Authenticate Devhub task" 4 | } 5 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/messages/analyze_with_PMD.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandDescription": "This task is used to run a static analysis of the apex classes and triggers using PMD, Please ensure that the SFDX CLI and sfpowerkit plugin are installed before using this task", 3 | "sourceDirectoryFlagDescription": "The directory that is to be analzed using PMD, If omitted default project diretory as mentioned in sfdx-project.json will be used", 4 | "rulesetFlagDescription": "Inbuilt is the default ruleset that comes with the task, If you choose custom, please provide the path to the ruleset", 5 | "rulesetPathFlagDescription": "The path to the ruleset if you are utilizing your own ruleset", 6 | "formatFlagDescription": "https://pmd.github.io/latest/pmd_userdocs_cli_reference.html#available-report-formats", 7 | "outputPathFlagDescription": "The file to which the output for static analysis will be written", 8 | "versionFlagDescription": "The version of PMD to be used for static analysis", 9 | "thresholdFlagDescription": "Violations with a priority less than or equal to the threshold will cause the command to fail", 10 | "isToBreakBuildFlagDescription": "Enable this option if the build should be reported as failure if 1 or more critical defects are reported during the analysis", 11 | "projectDirectoryFlagDescription": "The project directory should contain a sfdx-project.json for this command to succeed", 12 | "refNameFlagDescription": "Reference name to be prefixed to output variables" 13 | } 14 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/messages/artifacts_query.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandDescription": "Fetch details about artifacts installed in a target org", 3 | "branchNameFlagDescription": "Repository branch in which the changelog files are located" 4 | } 5 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/messages/create-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandDescription": "Creates a versioned artifact from a source directory containing SFDMU-based data (in csv format and export json). The artifact can be consumed by release pipelines, to deploy the data to orgs", 3 | "packageFlagDescription": "The name of the package", 4 | "versionNumberFlagDescription": "The format is major.minor.patch.buildnumber . This will override the build number mentioned in the sfdx-project.json, Try considering the use of Increment Version Number task before this task", 5 | "projectDirectoryFlagDescription": "The project directory should contain a sfdx-project.json for this command to succeed", 6 | "artifactDirectoryFlagDescription": "The directory where the artifact is to be written", 7 | "diffCheckFlagDescription": "Only build when the package has changed", 8 | "branchFlagDescription": "The git branch that this build is triggered on, Useful for metrics and general identification purposes", 9 | "gitTagFlagDescription": "Tag the current commit ID with an annotated tag containing the package name and version - does not push tag", 10 | "repoUrlFlagDescription": "Custom source repository URL to use in artifact metadata, overrides origin URL defined in git config", 11 | "refNameFlagDescription": "Reference name to be prefixed to output variables", 12 | "configFilePathFlagDescription": "Path in the current project directory containing config file for the packaging org" 13 | } 14 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/messages/create_data_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandDescription": "Creates a versioned artifact from a source directory containing SFDMU-based data (in csv format and export json). The artifact can be consumed by release pipelines, to deploy the data to orgs", 3 | "packageFlagDescription": "The name of the package", 4 | "versionNumberFlagDescription": "The format is major.minor.patch.buildnumber . This will override the build number mentioned in the sfdx-project.json, Try considering the use of Increment Version Number task before this task", 5 | "projectDirectoryFlagDescription": "The project directory should contain a sfdx-project.json for this command to succeed", 6 | "artifactDirectoryFlagDescription": "The directory where the artifact is to be written", 7 | "diffCheckFlagDescription": "Only build when the package has changed", 8 | "branchFlagDescription": "The git branch that this build is triggered on, Useful for metrics and general identification purposes", 9 | "gitTagFlagDescription": "Tag the current commit ID with an annotated tag containing the package name and version - does not push tag", 10 | "repoUrlFlagDescription": "Custom source repository URL to use in artifact metadata, overrides origin URL defined in git config", 11 | "refNameFlagDescription": "Reference name to be prefixed to output variables" 12 | } 13 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/messages/create_delta_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandDescription": "This task is used to create a delta package between two commits and bundle the created delta as as a deployable artifact. Please ensure that the SFDX CLI and sfpowerkit plugin are installed before using this task", 3 | "packageNameFlagDescription": "The name of the package", 4 | "revisionFromFlagDescription": "Provide the full SHA Commit ID, from where the diff should start generating", 5 | "revisionToFlagDescription": "If not set, the head commit ID of the current branch is used", 6 | "repoUrlFlagDescription": "Custom source repository URL to use in artifact metadata, overrides origin URL defined in git config", 7 | "projectDirectoryFlagDescription": "The project directory should contain a sfdx-project.json for this command to succeed", 8 | "artifactDirectoryFlagDescription": "The directory where the artifact is to be written", 9 | "branchFlagDescription": "The git branch that this build is triggered on, Useful for metrics and general identification purposes", 10 | "versionNameFlagDescription": "Provide a meaningful name such as the default value, so this artifact can be identified in the release", 11 | "generateDestructiveManifestFlagDescription": "Check this option to generate a destructive manifest to be deployed", 12 | "refNameFlagDescription": "Reference name to be prefixed to output variables" 13 | } 14 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/messages/create_source_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandDescription": "This task simulates a packaging experience similar to unlocked packaging - creating an artifact that consists of the metadata (e.g. commit Id), source code & an optional destructive manifest. The artifact can then be consumed by release pipelines, to deploy the package", 3 | "packageFlagDescription": "The name of the package", 4 | "versionNumberFlagDescription": "The format is major.minor.patch.buildnumber . This will override the build number mentioned in the sfdx-project.json, Try considering the use of Increment Version Number task before this task", 5 | "projectDirectoryFlagDescription": "The project directory should contain a sfdx-project.json for this command to succeed", 6 | "destructiveManiFestFilePathFlagDescription": "Path to a destructiveChanges.xml, mentioning any metadata that need to be deleted before the contents in the source package need to be installed in the org", 7 | "artifactDirectoryFlagDescription": "The directory where the artifact is to be written", 8 | "diffCheckFlagDescription": "Only build when the package has changed", 9 | "branchFlagDescription": "The git branch that this build is triggered on, Useful for metrics and general identification purposes", 10 | "gitTagFlagDescription": "Tag the current commit ID with an annotated tag containing the package name and version - does not push tag", 11 | "repoUrlFlagDescription": "Custom source repository URL to use in artifact metadata, overrides origin URL defined in git config", 12 | "refNameFlagDescription": "Reference name to be prefixed to output variables" 13 | } 14 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/messages/dependency_expand.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandDescription":"Expand the dependency list in sfdx-project.json file for each package, fix the gap of dependencies from its dependent packages", 3 | "overWriteProjectConfigFlagDescription":"Flag to overwrite existing sfdx-project.json file" 4 | } -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/messages/dependency_install.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandDescription":"Install all the external dependencies of a given project", 3 | "installationkeysFlagDescription":"Installation key for key-protected packages (format is packagename:key --> core:key nCino:key vlocity:key to allow some packages without installation key)" 4 | } -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/messages/dependency_shrink.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandDescription":"Shrink the dependency list in sfdx-project.json file for each package, remove duplicate dependencies that already exist in its dependent packages", 3 | "overWriteProjectConfigFlagDescription":"Flag to overwrite existing sfdx-project.json file" 4 | } -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/messages/deploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandDescription": "Deploy packages from the provided aritfact directory, to a given org, using the order and configurable flags provided in sfdx-project.json", 3 | "targetOrgFlagDescription": "Alias/User Name of the target environment", 4 | "artifactDirectoryFlagDescription": "The directory containing artifacts to be deployed", 5 | "waitTimeFlagDescription": "Wait time for command to finish in minutes", 6 | "logsGroupSymbolFlagDescription": "Symbol used by CICD platform to group/collapse logs in the console. Provide an opening group, and an optional closing group symbol.", 7 | "tagFlagDescription": "Tag the deploy with a label, useful for identification in metrics", 8 | "validateModeFlagDescription": "Enable for validation deployments", 9 | "skipIfAlreadyInstalled": "Skip the package installation if the package is already installed in the org", 10 | "baselineorgFlagDescription": "The org against which the package skip should be baselined", 11 | "allowUnpromotedPackagesFlagDescription": "Allow un-promoted packages to be installed in production", 12 | "retryOnFailureFlagDescription": "Retry on a failed deployment of a package", 13 | "configFileFlagDescription":"Path to the config file which determines how the packages are deployed based on the filters in release config", 14 | "enableSourceTrackingFlagDescription": "Enable source tracking on the packages being deployed to an org" 15 | } 16 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/messages/fetch.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandDescription": "Fetch artifacts from an artifact registry that is either NPM compatible or supports universal artifacts", 3 | "releaseDefinitionFlagDescription": "Path to YAML file containing map of packages and package versions to download", 4 | "artifactDirectoryFlagDescription": "Directory to save downloaded artifacts", 5 | "scriptPathFlagDescription": "(Optional: no-NPM) Path to script that authenticates and downloads artifacts from the registry", 6 | "npmFlagDescription": "Download artifacts from a pre-authenticated private npm registry", 7 | "scopeFlagDescription": "(required for NPM) User or Organisation scope of the NPM package", 8 | "npmrcPathFlagDescription": "Path to .npmrc file used for authentication to registry. If left blank, defaults to home directory", 9 | "logsGroupSymbolFlagDescription": "Symbol used by CICD platform to group/collapse logs in the console. Provide an opening group, and an optional closing group symbol." 10 | } 11 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/messages/generate_changelog.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandDescription": "Generates release changelog, providing a summary of artifact versions, work items and commits introduced in a release. Creates a release definition based on artifacts contained in the artifact directory, and compares it to previous release definition in changelog stored on a source repository", 3 | "limitFlagDescription": "limit the number of releases to display in changelog markdown", 4 | "artifactDirectoryFlagDescription": "Directory containing sfpowerscripts artifacts", 5 | "releaseNameFlagDescription": "Name of the release for which to generate changelog", 6 | "workItemFilterFlagDescription": "Regular expression used to search for work items (user stories) introduced in release", 7 | "workItemUrlFlagDescription": "Generic URL for work items. Each work item ID will be appended to the URL, providing quick access to work items", 8 | "repoUrlFlagDescription": "Repository in which the changelog files are located. Assumes user is already authenticated.", 9 | "branchNameFlagDescription": "Repository branch in which the changelog files are located", 10 | "noPushFlagDescription":"Do not push the changelog to a repository to the provided branch", 11 | "showAllArtifactsFlagDescription": "Show all artifacts in changelog markdown, including those that have not changed in the release", 12 | "forcePushFlagDescription": "Force push changes to the repository branch", 13 | "directoryFlagDescription": "Relative path to directory to which the release defintion file should be generated, if the directory doesnt exist, it will be created" 14 | } 15 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/messages/impact_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandDescription": "Figures out impacted packages of a project, due to a change from the last known tags", 3 | "baseCommitOrBranchFlagDescription": "The base branch on which the git tags should be used" 4 | } 5 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/messages/impact_release_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandDescription": "Figures out impacted release configurations of a project, due to a change,from the last known tags", 3 | "releaseConfigFileFlagDescription":"Path to the directory containing release defns", 4 | "baseCommitOrBranchFlagDescription": "The base branch on which the git tags should be used from", 5 | "filterByFlagDescription": "Filter by a specific release config name", 6 | "explictDependencyCheckFlagDescription": "Activate to consider dependencyOn attribut while handling impact" 7 | } 8 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/messages/increment_build_number.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandDescription": "Increment the selected version counter by one and optionally commit changes to sfdx-project.json. This command does not push changes to the source repository", 3 | "segmentFlagDescription": "Select the segment of the version", 4 | "appendBuildNumberFlagDescription": "Set the build segment of the version number to the build number rather than incremenenting", 5 | "packageFlagDescription": "The name of the package of which the version need to be incremented,If not specified the default package is utilized", 6 | "projectDirectoryFlagDescription": "The directory should contain a sfdx-project.json for this command to succeed", 7 | "commitChangesFlagDescription": "Mark this if you want to commit the modified sfdx-project json, Please note this will not push to the repo only commits in the local checked out repo, You would need to have a push to the repo at the end of the packaging task if everything is successfull", 8 | "refNameFlagDescription": "Reference name to be prefixed to output variables", 9 | "runNumberFlagDescription": "The build number of the CI pipeline, usually available through an environment variable" 10 | } 11 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/messages/install_data_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandDescription": "(DEPRECATED) Installs a SFDMU-based data package consisting of csvfiles and export.json to a target org", 3 | "packageFlagDescription": "Name of the package to be installed", 4 | "targetOrgFlagDescription": "Alias/User Name of the target environment", 5 | "artifactDirectoryFlagDescription": "The directory where the artifact is located", 6 | "skipOnMissingArtifactFlagDescription": "Skip package installation if the build artifact is missing. Enable this if artifacts are only being created for modified packages", 7 | "skipIfAlreadyInstalled": "Skip the package installation if the package is already installed in the org" 8 | } 9 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/messages/install_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandDescription": "Installs a sfpowerscripts artifact to an org", 3 | "packageFlagDescription": "Name of the package to be installed", 4 | "targetOrgFlagDescription": "Alias/User Name of the target environment", 5 | "apexCompileOnlyPackageFlagDescription": "(unlocked) package installation triggers a compilation of apex, flag to trigger compilation of package only", 6 | "artifactDirectoryFlagDescription": "The directory where the artifact is located", 7 | "securityTypeFlagDescription": "(unlocked) Select the security access for the package installation", 8 | "optimizedeployment": "(source) Optimize deployment by triggering test classes that are in the package, rather than using the whole tests in the org", 9 | "skiptesting": "(source) Skips running test when deploying to a sandbox", 10 | "upgradeTypeFlagDescription": "(unlocked)the upgrade type for the package installation", 11 | "waitTimeFlagDescription": "wait time for command to finish in minutes", 12 | "publishWaitTimeFlagDescription": "(unlocked) number of minutes to wait for subscriber package version ID to become available in the target org", 13 | "skipIfAlreadyInstalled": "Skip the installation if the package is already installed in the org" 14 | } 15 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/messages/install_package_command.json: -------------------------------------------------------------------------------- 1 | { 2 | "packageFlagDescription": "Name of the package to be installed", 3 | "targetOrgFlagDescription": "Alias/User Name of the target environment", 4 | "artifactDirectoryFlagDescription": "The directory where the artifact is located", 5 | "skipOnMissingArtifactFlagDescription": "Skip package installation if the build artifact is missing. Enable this if artifacts are only being created for modified packages" 6 | } 7 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/messages/install_source_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandDescription": "(DEPRECATED) Installs a sfpowerscripts source package to the target org", 3 | "packageFlagDescription": "Name of the package to be installed", 4 | "targetOrgFlagDescription": "Alias/User Name of the target environment", 5 | "artifactDirectoryFlagDescription": "The directory where the artifact is located", 6 | "skipOnMissingArtifactFlagDescription": "Skip package installation if the build artifact is missing. Enable this if artifacts are only being created for modified packages", 7 | "waitTimeFlagDescription": "wait time for command to finish in minutes", 8 | "optimizedeployment": "Optimize deployment by triggering test classes that are in the package, rather than using the whole tests in the org", 9 | "skiptesting": "Skips running test when deploying to a sandbox", 10 | "skipIfAlreadyInstalled": "Skip the package installation if the package is already installed in the org", 11 | "refNameFlagDescription": "Reference name to be prefixed to output variables" 12 | } 13 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/messages/install_unlocked_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandDescription": "(DEPRECATED) Installs an unlocked package using sfpowerscripts metadata", 3 | "packageFlagDescription": "Name of the package to be installed", 4 | "targetOrgFlagDescription": "Alias/User Name of the target environment", 5 | "packageInstalledFromFlagDescription": "automatically retrieve the version ID of the package to be installed, from the build artifact", 6 | "packageVersionIdFlagDescription": "manually input package version Id of the package to be installed", 7 | "installationKeyFlagDescription": "installation key for key-protected package", 8 | "apexCompileOnlyPackageFlagDescription": "Each package installation triggers a compilation of apex, flag to trigger compilation of package only", 9 | "artifactDirectoryFlagDescription": "The directory where the artifact is located", 10 | "securityTypeFlagDescription": "Select the security access for the package installation", 11 | "skipOnMissingArtifactFlagDescription": "Skip package installation if the build artifact is missing. Enable this if artifacts are only being created for modified packages", 12 | "upgradeTypeFlagDescription": "the upgrade type for the package installation", 13 | "waitTimeFlagDescription": "wait time for command to finish in minutes", 14 | "publishWaitTimeFlagDescription": "number of minutes to wait for subscriber package version ID to become available in the target org", 15 | "skipIfAlreadyInstalled": "Skip the package installation if the package is already installed in the org" 16 | } 17 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/messages/metrics_report.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandDescription": "Report a custom metric to any sfpowerscripts supported metric provider" 3 | } 4 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/messages/org_profile_diff.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandDescription": "Compare profiles from project against target org or between two orgs (source and target)", 3 | "profileListFlagDescription": "List of profiles to compare, comma separated profile names. If not provided and no sourceusername is provided, all profiles from the source folder will be processed.", 4 | "sourceUsernameDescription": "Source org. If no profile is provided in the profilelist parameter, all the profile from this org will be fetched", 5 | "outputFolderDescription": "Output folder. Provide the output folder if comparing profiles from source org." 6 | } 7 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/messages/patch.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandDescription": "[Alpha] Patch a branch in the repository as per the release definition and create a new branch", 3 | "releaseDefinitionFlagDescription": "Path to release definiton yaml", 4 | "sourcebranchNameFlagDescription": "Name of the source branch to be used on which the alignment need to be applied", 5 | "targetbranchNameFlagDescription": "Name of the target branch to be created after the alignment", 6 | "scriptPathFlagDescription": "(Optional: no-NPM) Path to script that authenticates and downloads artifacts from the registry", 7 | "npmFlagDescription": "Download artifacts from a pre-authenticated private npm registry", 8 | "scopeFlagDescription": "(required for NPM) User or Organisation scope of the NPM package", 9 | "npmrcPathFlagDescription": "Path to .npmrc file used for authentication to registry. If left blank, defaults to home directory", 10 | "logsGroupSymbolFlagDescription": "Symbol used by CICD platform to group/collapse logs in the console. Provide an opening group, and an optional closing group symbol." 11 | } 12 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/messages/pool_delete.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandDescription": "Deletes the pooled scratch orgs from the Scratch Org Pool", 3 | "tagDescription": "tag used to identify the scratch org pool", 4 | "mypoolDescription": "Filter only Scratch orgs created by current user in the pool", 5 | "allscratchorgsDescription": "Deletes all used and unused Scratch orgs from pool by the tag", 6 | "inprogressonlyDescription": "Deletes all In Progress Scratch orgs from pool by the tag", 7 | "recoverOrphanedScratchOrgsDescription": "Recovers scratch orgs that were created by salesforce but were not tagged to sfpowerscripts due to timeouts etc." 8 | } 9 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/messages/prepare.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandDescription": "Prepare a pool of scratchorgs with all the packages upfront, so that any incoming change can be validated in an optimized manner", 3 | "poolConfigFlagDescription": "The path to the configuration file for creating a pool of scratch orgs", 4 | "keysDescription": "Keys to be used while installing any managed package dependencies. Required format is a string of key-value pairs separated by spaces e.g. packageA:pw123 packageB:pw123 packageC:pw123", 5 | "apiversion": "API version to be used", 6 | "npmrcPathFlagDescription": "Path to .npmrc file used for authentication to a npm registry when using npm based artifacts. If left blank, defaults to home directory", 7 | "logsGroupSymbolFlagDescription": "Symbol used by CICD platform to group/collapse logs in the console. Provide an opening group, and an optional closing group symbol." 8 | } 9 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/messages/profile_merge.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandDescription": "This command is used in the lower environments such as ScratchOrgs , Development / System Testing Sandboxes, inorder to apply the changes made in the environment to retrieved profile, so that it can be deployed to the higher environments", 3 | "folderFlagDescription": "comma separated list of folders to scan for profiles. If ommited, the folders in the packageDirectories configuration will be used.", 4 | "profileListFlagDescription": "comma separated list of profiles. If ommited, all the profiles found in the folder(s) will be merged", 5 | "metadataFlagDescription": "comma separated list of metadata for which the permissions will be retrieved.", 6 | "deleteFlagDescription": "set this flag to delete profile files that does not exist in the org." 7 | } 8 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/messages/profile_reconcile.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandDescription": "This command is used in the lower environments such as ScratchOrgs , Development / System Testing Sandboxes, where a retrieved profile from production has to be cleaned up only for the metadata that is contained in the environment or base it only as per the metadata that is contained in the packaging directory.", 3 | "folderFlagDescription": "path to the folder which contains the profiles to be reconciled,if project contain multiple package directories, please provide a comma seperated list, if omitted, all the package directories will be checked for profiles", 4 | "nameFlagDescription": "list of profiles to be reconciled. If ommited, all the profiles components will be reconciled.", 5 | "destFolderFlagDescription": " the destination folder for reconciled profiles, if omitted existing profiles will be reconciled and will be rewritten in the current location", 6 | "sourceonlyFlagDescription": "set this flag to reconcile profiles only against component available in the project only. Configure ignored perissions in sfdx-project.json file in the array plugins->sfpowerkit->ignoredPermissions.", 7 | "targetorgFlagDescription": " org against which profiles will be reconciled. this parameter can be ommited if sourceonly flag is set." 8 | } 9 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/messages/profile_retrieve.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandDescription": "Retrieve profiles from the salesforce org with all its associated permissions. Common use case for this command is to migrate profile changes from a integration environment to other higher environments [overcomes SFDX CLI Profile retrieve issue where it doesnt fetch the full profile unless the entire metadata is present in source], or retrieving profiles from production to lower environments for testing.", 3 | "folderFlagDescription": "retrieve only updated versions of profiles found in this directory, If ignored, all profiles will be retrieved.", 4 | "profileListFlagDescription": "comma separated list of profiles to be retrieved. Use it for selectively retrieving an existing profile or retrieving a new profile", 5 | "deleteFlagDescription": "set this flag to delete profile files that does not exist in the org, when retrieving in bulk", 6 | "retriveDelayWarning":"Retrieving profiles may take a significant time depending on the number of profiles \nand managed package components installed in your org,Please be patient" 7 | } 8 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/messages/promote.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandDescription": "Promotes validated unlocked packages with code coverage greater than 75%", 3 | "artifactDirectoryFlagDescription": "The directory where artifacts are located", 4 | "outputDirectoryFlagDescription": "Output directory where promoted artifacts are written", 5 | "devhubAliasFlagDescription": "Provide the alias of the devhub previously authenticated, default value is HubOrg if using the Authenticate Devhub task", 6 | "logsGroupSymbolFlagDescription": "Symbol used by CICD platform to group/collapse logs in the console. Provide an opening group, and an optional closing group symbol." 7 | } 8 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/messages/releasedefinition_generate.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandDescription": "Generates release definition based on the artifacts installed from a commit reference", 3 | "configFileFlagDescription":"Path to the config file which determines how the release definition should be generated", 4 | "releaseNameFlagDescription": "Set a release name on the release definition file created", 5 | "commitFlagDescription": "Utilize the tags on the source branch to generate release definiton", 6 | "directoryFlagDescription": "Relative path to directory to which the release definition file should be generated, if the directory doesnt exist, it will be created", 7 | "branchNameFlagDescription": "Repository branch in which the release definition files are to be written", 8 | "noPushFlagDescription":"Do not push the changelog to a repository to the provided branch", 9 | "forcePushFlagDescription": "Force push changes to the repository branch" 10 | } 11 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/messages/scratchorg_poolFetch.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandDescription": "Gets an active/unused scratch org from the scratch org pool", 3 | "tagDescription": "(required) tag used to identify the scratch org pool", 4 | "mypoolDescription": "Filter the tag for any additions created by the executor of the command", 5 | "sendToUserDescription": "Send the credentials of the fetched scratchorg to another DevHub user, Useful for situations when pool is only limited to certain users", 6 | "aliasDescription": "Fetch and set an alias for the org", 7 | "setdefaultusernameDescription": "set the authenticated org as the default username that all commands run against", 8 | "noSourceTrackingDescription": "Do not set source tracking while fetching the scratch org" 9 | } 10 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/messages/scratchorg_pool_metrics_publish.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandDescription": "Publish metrics about scratch org pools to your observability platform, via StatsD or direct APIs for supported platforms" 3 | } 4 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/messages/scratchorg_pool_org_delete.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandDescription": "Deletes a particular scratch org in the pool, This command is to be used in a pipeline with correct permissions to delete any active scratch org record or to be used by an adminsitrator", 3 | "userNameFlagDescription": "Username of the scratchOrg to be deleted, not aliases" 4 | } 5 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/messages/scratchorg_poollist.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandDescription": "Retrieves a list of active scratch org and details from any pool. If this command is run with -m|--mypool, the command will retrieve the passwords for the pool created by the user who is executing the command.", 3 | "tagDescription": "tag used to identify the scratch org pool", 4 | "mypoolDescription": "Filter the tag for any additions created by the executor of the command", 5 | "allscratchorgsDescription": "Gets all used and unused Scratch orgs from pool" 6 | } 7 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/messages/trigger_apex_test.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandDescription": "Triggers Apex unit test in an org. Supports test level RunAllTestsInPackage, which optionally allows validation of individual class code coverage", 3 | "targetOrgFlagDescription": "username or alias for the target org; overrides default target org", 4 | "testLevelFlagDescription": "The test level of the test that need to be executed when the code is to be deployed", 5 | "packageFlagDescription": "Name of the package to run tests. Required when test level is RunAllTestsInPackage", 6 | "synchronousFlagDescription": "Select an option if the tests are to be run synchronously", 7 | "specifiedTestsFlagDescription": "comma-separated list of Apex test class names or IDs and, if applicable, test methods to run", 8 | "apexTestSuiteFlagDescription": "comma-separated list of Apex test suite names to run", 9 | "validateIndividualClassCoverageFlagDescription": "Validate that individual classes have a coverage greater than the minimum required percentage coverage, only available when test level is RunAllTestsInPackage", 10 | "validatePackageCoverageFlagDescription": "Validate that the package coverage is greater than the minimum required percentage coverage, only available when test level is RunAllTestsInPackage", 11 | "coveragePercentFlagDescription": "Minimum required percentage coverage, when validating code coverage", 12 | "waitTimeFlagDescription": "wait time for command to finish in minutes" 13 | } 14 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/messages/validate_apex_coverage.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandDescription": "Validates apex test coverage in the org, Please ensure that the SFDX CLI and sfpowerkit plugin are installed before using this task.", 3 | "targetOrgFlagDescription": "Alias or username of the target org", 4 | "testCoverageFlagDescription": "The percentage of test coverage for apex clasess, that should be as per the last test run status" 5 | } 6 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/src/errors/ReleaseError.ts: -------------------------------------------------------------------------------- 1 | import SfpowerscriptsError from './SfpowerscriptsError'; 2 | 3 | import { ReleaseResult } from '../impl/release/ReleaseImpl'; 4 | 5 | export default class ReleaseError extends SfpowerscriptsError { 6 | /** 7 | * Payload for the results of the release 8 | */ 9 | readonly data: ReleaseResult; 10 | 11 | /** 12 | * The underlying error that caused this error to be raised 13 | */ 14 | readonly cause: Error; 15 | 16 | constructor(message: string, data: ReleaseResult, cause?: Error) { 17 | super(message); 18 | 19 | this.data = data; 20 | this.cause = cause; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/src/errors/SfpowerscriptsError.ts: -------------------------------------------------------------------------------- 1 | export default abstract class SfpowerscriptsError extends Error { 2 | readonly message: string; 3 | readonly code: string; 4 | /** 5 | * The underlying error that caused this error to be raised 6 | */ 7 | readonly cause: Error; 8 | /** 9 | * Additional payload for the error 10 | */ 11 | abstract data: unknown; 12 | 13 | constructor(message: string, code?: string, cause?: Error) { 14 | super(message); 15 | 16 | this.message = message; 17 | this.code = code; 18 | this.cause = cause; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/src/errors/ValidateError.ts: -------------------------------------------------------------------------------- 1 | import SfpowerscriptsError from './SfpowerscriptsError'; 2 | import ValidateResult from '../impl/validate/ValidateResult'; 3 | 4 | export default class ValidateError extends SfpowerscriptsError { 5 | /** 6 | * Payload for the results of the release 7 | */ 8 | readonly data: ValidateResult; 9 | 10 | /** 11 | * The underlying error that caused this error to be raised 12 | */ 13 | readonly cause: Error; 14 | 15 | constructor(message: string, data: ValidateResult, cause?: Error) { 16 | super(message); 17 | 18 | this.data = data; 19 | this.cause = cause; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/src/impl/Stage.ts: -------------------------------------------------------------------------------- 1 | export enum Stage { 2 | ANALYZE = 'analyze', 3 | PREPARE = 'prepare', 4 | VALIDATE = 'validate', 5 | QUICKBUILD = 'quickbuild', 6 | BUILD = 'build', 7 | DEPLOY = 'deploy', 8 | PROMOTE = 'promote', 9 | PUBLISH = 'publish' 10 | } 11 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/src/impl/artifacts/FetchAnArtifact.ts: -------------------------------------------------------------------------------- 1 | export default interface FetchAnArtifact { 2 | fetchArtifact( 3 | packageName: string, 4 | artifactDirectory: string, 5 | version: string, 6 | isToContinueOnMissingArtifact: boolean 7 | ): void; 8 | } 9 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/src/impl/artifacts/FetchArtifactSelector.ts: -------------------------------------------------------------------------------- 1 | import FetchAnArtifact from './FetchAnArtifact'; 2 | import { FetchAnArtifactFromNPM } from './FetchAnArtifactFromNPM'; 3 | import { FetchAnArtifactUsingScript } from './FetchAnArtifactUsingScript'; 4 | 5 | export default class FetchArtifactSelector { 6 | constructor(private fetchArtifactScript?: string, private scope?: string, private npmrcPath?: string) {} 7 | 8 | public getArtifactFetcher(): FetchAnArtifact { 9 | if (this.fetchArtifactScript) { 10 | return new FetchAnArtifactUsingScript(this.fetchArtifactScript); 11 | } else if (this.scope) { 12 | return new FetchAnArtifactFromNPM(this.scope, this.npmrcPath); 13 | } else { 14 | throw new Error('Unable to determine artifact fetcher'); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/src/impl/artifacts/FetchArtifactsError.ts: -------------------------------------------------------------------------------- 1 | 2 | import SfpowerscriptsError from '../../errors/SfpowerscriptsError'; 3 | import { ArtifactVersion } from './FetchImpl'; 4 | 5 | 6 | export default class FetchArtifactsError extends SfpowerscriptsError { 7 | /** 8 | * Payload consisting of artifacts that succeeded and failed to fetch 9 | */ 10 | readonly data: { 11 | success: ArtifactVersion[]; 12 | failed: ArtifactVersion[]; 13 | }; 14 | 15 | /** 16 | * The underlying error that caused this error to be raised 17 | */ 18 | readonly cause: Error; 19 | 20 | constructor(message: string, data: { success:ArtifactVersion[]; failed: ArtifactVersion[] }, cause: Error) { 21 | super(message); 22 | 23 | this.data = data; 24 | this.cause = cause; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/src/impl/changelog/ReadPackageChangelog.ts: -------------------------------------------------------------------------------- 1 | import { Changelog as PackageChangelog } from '@dxatscale/sfpowerscripts.core/lib/changelog/interfaces/GenericChangelogInterfaces'; 2 | 3 | export default interface ReadPackageChangelog { 4 | (changelogFilePath: string): PackageChangelog; 5 | } 6 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/src/impl/changelog/ReleaseChangelog.ts: -------------------------------------------------------------------------------- 1 | import { Changelog, Commit } from '@dxatscale/sfpowerscripts.core/lib/changelog/interfaces/GenericChangelogInterfaces'; 2 | 3 | export class ReleaseChangelog { 4 | releases: Release[]; 5 | orgs?: org[]; 6 | } 7 | 8 | export class org { 9 | /** 10 | * Name of the org 11 | */ 12 | name: string; 13 | 14 | /** 15 | * History of releases to the org 16 | */ 17 | releases: ReleaseId[]; 18 | 19 | /** 20 | * Latest release deployed to org 21 | */ 22 | latestRelease: ReleaseId; 23 | 24 | /** 25 | * Number of consecutive deployments of the latest release to the org 26 | */ 27 | retryCount: number; 28 | } 29 | 30 | export class ReleaseId { 31 | names: string[]; 32 | buildNumber: number; 33 | hashId: string; 34 | date?:string; 35 | } 36 | 37 | export interface Release extends ReleaseId { 38 | workItems: any; 39 | artifacts: Artifact[]; 40 | } 41 | 42 | export interface Artifact extends Changelog { 43 | /** 44 | * Name of the artifact 45 | */ 46 | name: string; 47 | 48 | /** 49 | * Commit Id from which previous artifact was created 50 | */ 51 | from: string; 52 | 53 | /** 54 | * Commit Id from which current artifact was created 55 | */ 56 | to: string; 57 | 58 | /** 59 | * Package version number 60 | */ 61 | version: string; 62 | 63 | /** 64 | * Latest commit Id in the package changelog 65 | */ 66 | latestCommitId: string; 67 | 68 | /** 69 | * Commits between previous artifact's package changelog and current artifact's package changelog 70 | */ 71 | commits: Commit[]; 72 | } 73 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/src/impl/deploy/PostDeployHook.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from '@dxatscale/sfp-logger'; 2 | import { PackageInstallationResult } from '@dxatscale/sfpowerscripts.core/lib/package/packageInstallers/PackageInstallationResult'; 3 | import SfpPackage from '@dxatscale/sfpowerscripts.core/lib/package/SfpPackage'; 4 | 5 | export interface PostDeployHook { 6 | postDeployPackage( 7 | sfpPackage: SfpPackage, 8 | packageInstallationResult: PackageInstallationResult, 9 | targetUsername: string, 10 | deployedPackages?:SfpPackage[], 11 | devhubUserName?: string, 12 | logger?:Logger 13 | ): Promise<{ isToFailDeployment: boolean; message?: string }>; 14 | } 15 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/src/impl/deploy/PreDeployHook.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from '@dxatscale/sfp-logger'; 2 | import SfpPackage from '@dxatscale/sfpowerscripts.core/lib/package/SfpPackage'; 3 | 4 | export interface PreDeployHook { 5 | preDeployPackage( 6 | sfpPackage: SfpPackage, 7 | targetUsername: string, 8 | deployedPackages?:SfpPackage[], 9 | devhubUserName?: string, 10 | logger?:Logger, 11 | ): Promise<{ isToFailDeployment: boolean; message?: string }>; 12 | } 13 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/src/impl/release/ReleaseDefinitionGeneratorConfigSchema.ts: -------------------------------------------------------------------------------- 1 | export default interface ReleaseDefinitionGeneratorSchema { 2 | includeOnlyArtifacts?: string[]; 3 | excludeArtifacts?: string[]; 4 | excludeArtifactsWithTag?: string[]; 5 | excludeAllPackageDependencies?:boolean; 6 | excludePackageDependencies?: string[]; 7 | includeOnlyPackageDependencies?: string[]; 8 | releasedefinitionProperties?: { 9 | skipIfAlreadyInstalled: boolean; 10 | skipArtifactUpdate:boolean; 11 | baselineOrg?: string; 12 | promotePackagesBeforeDeploymentToOrg?: string; 13 | changelog?: { 14 | repoUrl?: string; 15 | workItemFilters?: string[]; 16 | workItemUrl?: string; 17 | limit?: number; 18 | showAllArtifacts?: boolean; 19 | }; 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/src/impl/release/ReleaseDefinitionSchema.ts: -------------------------------------------------------------------------------- 1 | export default interface ReleaseDefinitionSchema { 2 | release: string; 3 | skipIfAlreadyInstalled: boolean; 4 | skipArtifactUpdate:boolean; 5 | baselineOrg?: string; 6 | artifacts: { 7 | [p: string]: string; 8 | }; 9 | packageDependencies?: { 10 | [p: string]: string; 11 | }; 12 | promotePackagesBeforeDeploymentToOrg?: string; 13 | changelog?: { 14 | repoUrl?: string; 15 | workItemFilter?:string; 16 | workItemFilters?: string[]; 17 | workItemUrl?: string; 18 | limit?: number; 19 | showAllArtifacts?: boolean; 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/src/impl/repo/AlignImpl.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from "@dxatscale/sfp-logger"; 2 | 3 | export interface AlignRepoProps { 4 | artifactDirectory: string; 5 | workingDirectory?:string 6 | } 7 | 8 | export class AlignImpl { 9 | constructor(private props: AlignRepoProps, private logger?: Logger) { 10 | 11 | } 12 | 13 | async align() 14 | { 15 | //Convert to SFPPacakge 16 | 17 | } 18 | 19 | 20 | } 21 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/src/impl/validate/Analyzer.ts: -------------------------------------------------------------------------------- 1 | import ChangedComponentsFetcher from "@dxatscale/sfpowerscripts.core/lib/dependency/ChangedComponentsFetcher"; 2 | import Component from "@dxatscale/sfpowerscripts.core/lib/dependency/Component"; 3 | 4 | 5 | export class Analyzer { 6 | 7 | 8 | 9 | private static changedComponents: Component[]; 10 | 11 | public constructor(protected baseBranch: string) { } 12 | 13 | 14 | /** 15 | * 16 | * @returns array of components that have changed, can be empty 17 | */ 18 | protected async getChangedComponents(): Promise { 19 | if (Analyzer.changedComponents) return Analyzer.changedComponents; 20 | else return new ChangedComponentsFetcher(this.baseBranch).fetch(); 21 | } 22 | } -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/src/impl/validate/ValidateResult.ts: -------------------------------------------------------------------------------- 1 | import { DeploymentResult, PackageInfo } from '../deploy/DeployImpl'; 2 | import DependencyViolation from '@dxatscale/sfpowerscripts.core/lib/dependency/DependencyViolation'; 3 | 4 | export default interface ValidateResult { 5 | deploymentResult?: DeploymentResult; 6 | dependencyViolations?: DependencyViolation[]; 7 | testFailures?: PackageInfo[]; 8 | } 9 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/src/index.ts: -------------------------------------------------------------------------------- 1 | export default {}; 2 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/src/outputs/FileOutputHandler.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs-extra'; 2 | import { EOL } from 'os'; 3 | import path from 'path'; 4 | 5 | export default class FileOutputHandler { 6 | static instance: FileOutputHandler; 7 | 8 | public static getInstance() { 9 | if (!FileOutputHandler.instance) 10 | FileOutputHandler.instance = new FileOutputHandler('.sfpowerscripts/outputs'); 11 | 12 | return FileOutputHandler.instance; 13 | } 14 | 15 | 16 | private constructor(private containerFolder: string) { 17 | fs.mkdirpSync(this.containerFolder); 18 | } 19 | 20 | public writeOutput(fileName: string, output: string ) { 21 | if (!fs.existsSync(path.join(this.containerFolder, fileName))) { 22 | fs.createFileSync(path.join(this.containerFolder, fileName)); 23 | } 24 | fs.writeFileSync(path.join(this.containerFolder, fileName), output); 25 | }; 26 | 27 | public appendOutput(fileName: string,output: string) { 28 | if (!fs.existsSync(path.join(this.containerFolder, fileName))) { 29 | fs.createFileSync(path.join(this.containerFolder, fileName)); 30 | } 31 | fs.appendFileSync(path.join(this.containerFolder, fileName), EOL); 32 | fs.appendFileSync(path.join(this.containerFolder, fileName), output); 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/src/ui/TableConstants.ts: -------------------------------------------------------------------------------- 1 | export const ZERO_BORDER_TABLE = { 2 | top: ' ', 3 | 'top-mid': ' ', 4 | 'top-left': ' ', 5 | 'top-right': ' ', 6 | bottom: ' ', 7 | 'bottom-mid': ' ', 8 | 'bottom-left': ' ', 9 | 'bottom-right': ' ', 10 | left: '', 11 | 'left-mid': '', 12 | mid: '', 13 | 'mid-mid': '', 14 | right: '', 15 | 'right-mid': '', 16 | middle: ' ', 17 | }; 18 | 19 | 20 | 21 | export const COLON_MIDDLE_BORDER_TABLE = { 22 | top: '', 23 | 'top-mid': '', 24 | 'top-left': '', 25 | 'top-right': '', 26 | bottom: '', 27 | 'bottom-mid': '', 28 | 'bottom-left': '', 29 | 'bottom-right': '', 30 | left: '', 31 | 'left-mid': '', 32 | mid: '', 33 | 'mid-mid': '', 34 | right: '', 35 | 'right-mid': '', 36 | middle: ':', 37 | }; 38 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/src/utils/FetchArtifactsFromOrg.ts: -------------------------------------------------------------------------------- 1 | 2 | export async function mapInstalledArtifactstoPkgAndCommits( 3 | installedArtifacts: any, 4 | ) { 5 | let packagesMappedToLastKnownCommitId: { [p: string]: string } = {}; 6 | if (installedArtifacts != null) { 7 | packagesMappedToLastKnownCommitId = 8 | getPackagesToCommits(installedArtifacts); 9 | } 10 | return packagesMappedToLastKnownCommitId; 11 | 12 | function getPackagesToCommits(installedArtifacts: any): { 13 | [p: string]: string; 14 | } { 15 | const packagesToCommits: { [p: string]: string } = {}; 16 | 17 | // Construct map of artifact and associated commit Id 18 | installedArtifacts.forEach((artifact) => { 19 | packagesToCommits[artifact.Name] = artifact.CommitId__c; 20 | //Override for debugging purposes 21 | if (process.env.VALIDATE_OVERRIDE_PKG) 22 | packagesToCommits[process.env.VALIDATE_OVERRIDE_PKG] = 23 | process.env.VALIDATE_PKG_COMMIT_ID; 24 | }); 25 | 26 | if (process.env.VALIDATE_REMOVE_PKG) 27 | delete packagesToCommits[process.env.VALIDATE_REMOVE_PKG]; 28 | 29 | return packagesToCommits; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/src/utils/Get18DigitSalesforceId.ts: -------------------------------------------------------------------------------- 1 | export default function get18DigitSalesforceId(recordId: string) { 2 | if (recordId && recordId.length === 18) { 3 | return recordId; 4 | } else if (recordId && recordId.length === 15) { 5 | let addon = ''; 6 | for (let block = 0; block < 3; block++) { 7 | let loop = 0; 8 | for (let position = 0; position < 5; position++) { 9 | let current = recordId.charAt(block * 5 + position); 10 | if (current >= 'A' && current <= 'Z') loop += 1 << position; 11 | } 12 | addon += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ012345'.charAt(loop); 13 | } 14 | let convertedId = recordId + addon; 15 | return convertedId; 16 | } else { 17 | throw new Error(`Invalid Salesforce Id ${recordId}`); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/sfpowerscripts-cli/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./lib", 5 | "rootDir": "./src", 6 | "skipLibCheck": true 7 | }, 8 | "exclude": ["node_modules", "dist", "tests"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/sfprofiles/.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | root = false 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /packages/sfprofiles/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'], 3 | parser: '@typescript-eslint/parser', 4 | plugins: ['@typescript-eslint'], 5 | root: true, 6 | rules: { 7 | // must disable the base rule as it can report incorrect errors 8 | 'no-unused-vars': "off", 9 | '@typescript-eslint/no-unused-vars': [ 10 | 'warn', // or "error" 11 | { 12 | argsIgnorePattern: '^_', 13 | varsIgnorePattern: '^_', 14 | caughtErrorsIgnorePattern: '^_', 15 | }, 16 | ], 17 | "prefer-const": "warn" 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /packages/sfprofiles/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | plugins: [ 4 | '@babel/plugin-proposal-optional-chaining', 5 | '@babel/plugin-proposal-nullish-coalescing-operator' 6 | ], 7 | }; -------------------------------------------------------------------------------- /packages/sfprofiles/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest/presets/js-with-babel', 3 | testEnvironment: 'node', 4 | restoreMocks: true, 5 | clearMocks: true, 6 | resetMocks: true, 7 | globals: { 8 | 'ts-jest': { 9 | tsconfig: 'tsconfig.json', 10 | babelConfig: true, 11 | }, 12 | }, 13 | transformIgnorePatterns: ['/node_modules/(?!@salesforce/source-deploy-retrieve)(.*)'], 14 | }; 15 | -------------------------------------------------------------------------------- /packages/sfprofiles/src/impl/source/worker.js: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path'; 2 | import { workerData } from 'worker_threads'; 3 | 4 | if (workerData.path.endsWith('.ts')) require('ts-node').register(); 5 | 6 | try { 7 | require(resolve(__dirname, workerData.path)); 8 | } catch (err) { 9 | console.log(err) 10 | } 11 | -------------------------------------------------------------------------------- /packages/sfprofiles/src/sfprofiles.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export default sfprofiles; 4 | 5 | function sfprofiles() { 6 | return "Hello from sfprofiles"; 7 | } 8 | -------------------------------------------------------------------------------- /packages/sfprofiles/src/utils/checkDeploymentStatus.ts: -------------------------------------------------------------------------------- 1 | import { Connection } from 'jsforce'; 2 | import { DeployResult } from 'jsforce/lib/api/metadata'; 3 | import { delay } from './delay'; 4 | import SFPLogger, {LoggerLevel } from '@dxatscale/sfp-logger'; 5 | 6 | export async function checkDeploymentStatus(conn: Connection, retrievedId: string): Promise { 7 | let metadata_result; 8 | 9 | while (true) { 10 | try { 11 | metadata_result = await conn.metadata.checkDeployStatus(retrievedId, true); 12 | } catch (error) { 13 | throw new Error(error.message); 14 | } 15 | 16 | if (!metadata_result.done) { 17 | SFPLogger.log('Polling for Deployment Status', LoggerLevel.INFO); 18 | await delay(5000); 19 | } else { 20 | break; 21 | } 22 | } 23 | return metadata_result; 24 | } 25 | -------------------------------------------------------------------------------- /packages/sfprofiles/src/utils/checkRetrievalStatus.ts: -------------------------------------------------------------------------------- 1 | import { Connection } from 'jsforce'; 2 | import { RetrieveResult } from 'jsforce/lib/api/metadata'; 3 | import { delay } from './delay'; 4 | import SFPLogger, {LoggerLevel } from '@dxatscale/sfp-logger'; 5 | 6 | 7 | export async function checkRetrievalStatus(conn: Connection, retrievedId: string, isToBeLoggedToConsole = true): Promise { 8 | let metadata_result; 9 | 10 | while (true) { 11 | try { 12 | metadata_result = await conn.metadata.checkRetrieveStatus(retrievedId); 13 | } catch (error) { 14 | throw new Error(error.message); 15 | } 16 | 17 | if (metadata_result.done === 'false') { 18 | if (isToBeLoggedToConsole) SFPLogger.log(`Polling for Retrieval Status`, LoggerLevel.INFO); 19 | await delay(5000); 20 | } else { 21 | //this.ux.logJson(metadata_result); 22 | break; 23 | } 24 | } 25 | return metadata_result; 26 | } 27 | -------------------------------------------------------------------------------- /packages/sfprofiles/src/utils/chunkArray.ts: -------------------------------------------------------------------------------- 1 | export function chunkArray(perChunk: number, inputArray: any[]): Array { 2 | let chunks = [], 3 | i = 0, 4 | n = inputArray.length; 5 | 6 | while (i < n) { 7 | chunks.push(inputArray.slice(i, (i += perChunk))); 8 | } 9 | 10 | return chunks; 11 | } 12 | -------------------------------------------------------------------------------- /packages/sfprofiles/src/utils/delay.ts: -------------------------------------------------------------------------------- 1 | export async function delay(ms: number) { 2 | return new Promise((resolve) => setTimeout(resolve, ms)); 3 | } 4 | -------------------------------------------------------------------------------- /packages/sfprofiles/src/utils/dxProjectManifestUtils.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as fs from 'fs-extra'; 3 | 4 | export class DXProjectManifestUtils { 5 | private sfdxProjectManifestJSON: any; 6 | 7 | public constructor(private projectFolder: string) {} 8 | 9 | public removePackagesNotInDirectory(): void { 10 | //Validate projectJson Path 11 | let sfdxProjectManifestPath = path.join(this.projectFolder, 'sfdx-project.json'); 12 | 13 | if (!fs.existsSync(sfdxProjectManifestPath)) 14 | throw new Error(`sfdx-project.json doesn't exist at ${sfdxProjectManifestPath}`); 15 | 16 | // Read sfdx-projec.json 17 | const sfdxProjectManifest = fs.readFileSync(sfdxProjectManifestPath, 'utf8'); 18 | this.sfdxProjectManifestJSON = JSON.parse(sfdxProjectManifest); 19 | 20 | //Filter sfdx-project.json of unwanted directories 21 | this.sfdxProjectManifestJSON.packageDirectories = this.sfdxProjectManifestJSON.packageDirectories.filter((el) => 22 | this.isElementExists(el) 23 | ); 24 | 25 | //write back sfdx-project.json back 26 | fs.writeJSONSync(sfdxProjectManifestPath, this.sfdxProjectManifestJSON); 27 | } 28 | 29 | private isElementExists(element) { 30 | return fs.existsSync(path.join(this.projectFolder, element.path)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/sfprofiles/src/utils/extract.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs-extra'; 2 | const unzipper = require('unzip-stream'); 3 | 4 | export async function extract(path: string, location: string) { 5 | return new Promise((resolve, reject) => { 6 | fs.createReadStream(path) 7 | .pipe(unzipper.Extract({ path: `${location}` })) 8 | .on('close', () => { 9 | resolve(); 10 | }) 11 | .on('error', (error) => reject(error)); 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /packages/sfprofiles/src/utils/getDefaults.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs-extra'; 2 | import * as path from 'path'; 3 | 4 | export default class GetDefaults { 5 | public static defaultConfig: any; 6 | private static init() { 7 | let resourcePath = path.join(__dirname, '..', '..', 'resources', 'default-config.json'); 8 | let fileData = fs.readFileSync(resourcePath, 'utf8'); 9 | this.defaultConfig = JSON.parse(fileData); 10 | } 11 | public static getApiVersion() { 12 | if (!this.defaultConfig) { 13 | this.init(); 14 | } 15 | return this.defaultConfig.apiversion; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/sfprofiles/src/utils/getPackageInfo.ts: -------------------------------------------------------------------------------- 1 | 2 | import { SfProjectJson } from '@salesforce/core'; 3 | import { JsonArray } from '@salesforce/ts-types'; 4 | 5 | //Returns the info about a requested package 6 | export function getPackageInfo(packageJson: SfProjectJson, packageName: string) { 7 | //Find the default package or passed package as the parameter 8 | const packageDirectories = (packageJson.get('packageDirectories') as JsonArray) || []; 9 | let packageInfo; 10 | if (packageName) { 11 | packageInfo = packageDirectories.filter((it) => { 12 | return it['package'] === packageName; 13 | })[0]; 14 | 15 | if (packageInfo == undefined) { 16 | throw new Error('Invalid Package'); 17 | } 18 | } else throw new Error('Package Name is empty'); 19 | return packageInfo; 20 | } 21 | 22 | //Returns the info about a requested package 23 | export function getDefaultPackageInfo(packageJson: SfProjectJson) { 24 | //Find the default package or passed package as the parameter 25 | const packageDirectories = (packageJson.get('packageDirectories') as JsonArray) || []; 26 | let packageInfo; 27 | 28 | packageInfo = packageDirectories.filter((it) => { 29 | return it['default'] == true; 30 | })[0]; 31 | 32 | if (packageInfo == undefined) { 33 | throw new Error('Default Package not found'); 34 | } 35 | 36 | return packageInfo; 37 | } 38 | -------------------------------------------------------------------------------- /packages/sfprofiles/src/utils/retrieveMetadata.ts: -------------------------------------------------------------------------------- 1 | import { Connection } from '@salesforce/core'; 2 | 3 | export async function retrieveMetadata(types: any, connection: Connection): Promise { 4 | const apiversion = await connection.retrieveMaxApiVersion(); 5 | let toReturn: Promise = new Promise((resolve, reject) => { 6 | connection.metadata.list(types, apiversion).then(metadata => { 7 | let metadata_fullnames = []; 8 | for (let i = 0; i < metadata.length; i++) { 9 | metadata_fullnames.push(metadata[i].fullName); 10 | } 11 | resolve(metadata_fullnames); 12 | }).catch(err => { 13 | return reject(err); 14 | }); 15 | }); 16 | 17 | return toReturn; 18 | } 19 | -------------------------------------------------------------------------------- /packages/sfprofiles/src/utils/xmlUtil.ts: -------------------------------------------------------------------------------- 1 | import * as xml2js from 'xml2js'; 2 | import * as util from 'util'; 3 | import * as fs from 'fs-extra'; 4 | import * as path from 'path'; 5 | import { AnyJson } from '@salesforce/ts-types'; 6 | import Profile from '@impl/metadata/schema'; 7 | 8 | export default class XmlUtil { 9 | public static async xmlToJSON(directory: string) { 10 | const parser = new xml2js.Parser({ explicitArray: false }); 11 | const parseString = util.promisify(parser.parseString) as _.Function1>; 12 | let obj = await parseString(fs.readFileSync(path.resolve(directory))); 13 | return obj; 14 | } 15 | public static jSONToXML(obj: AnyJson) { 16 | const builder = new xml2js.Builder(); 17 | let xml = builder.buildObject(obj); 18 | return xml; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/sfprofiles/src/utils/zipDirectory.ts: -------------------------------------------------------------------------------- 1 | const archiver = require('archiver'); 2 | import * as fs from 'fs-extra'; 3 | 4 | export async function zipDirectory(source, out) { 5 | const archive = archiver('zip', { zlib: { level: 9 } }); 6 | const stream = fs.createWriteStream(out); 7 | 8 | return new Promise((resolve, reject) => { 9 | archive 10 | .directory(source, false) 11 | .on('error', (err) => reject(err)) 12 | .pipe(stream); 13 | 14 | stream.on('close', () => resolve()); 15 | archive.finalize(); 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /packages/sfprofiles/tests/sfprofiles.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // import sfprofiles from '..'; 4 | // import { strict as assert } from 'assert'; 5 | 6 | // assert.strictEqual(sfprofiles(), 'Hello from sfprofiles'); 7 | // console.info("sfprofiles tests passed"); 8 | -------------------------------------------------------------------------------- /packages/sfprofiles/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "moduleResolution": "node", 5 | "experimentalDecorators": true, 6 | "outDir": "./lib", 7 | "rootDir": "./src", 8 | "skipLibCheck": true, 9 | "declarationMap": true, 10 | "baseUrl": "src", 11 | "paths": { 12 | "@utils/*": ["utils/*"], 13 | "@impl/*": ["impl/*"] 14 | }, 15 | "allowJs": true 16 | 17 | }, 18 | "include": ["./src/**/*"] 19 | } 20 | 21 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/**' -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "composite": true, 5 | "outDir": "./lib", 6 | "rootDir": ".", 7 | "inlineSourceMap": true, 8 | "declaration": true, 9 | "noImplicitAny": false, 10 | "esModuleInterop": true, 11 | "module": "commonjs", 12 | "target": "ES2018", 13 | "lib": ["ES2018"], 14 | "resolveJsonModule": true 15 | }, 16 | "exclude": ["node_modules", "lib"] 17 | } 18 | --------------------------------------------------------------------------------