diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index cb71291d3..000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,38 +0,0 @@ -# Add a new deploy command - -The deploy-retrieve plugin does not have any commands for deploying or or retrieving specific pieces of a salesforce project (e.g. metadata to a scratch or functions to a compute environment). Instead, we ask developers to create their own plugin with those commands. In order for the `deploy` or `retrieve` commands to know about the individual plugins, each plugin must implement an [oclif hook](https://oclif.io/docs/hooks) which returns [`Deployers`](https://github.com/salesforcecli/plugin-deploy-retrieve-utils/blob/main/src/deployer.ts) and `Retrievers`. - -This method allows developers to own their own plugins while also allowing a simple way for the overarching `project` topic to interact with those plugins. - -## Deploy - -To implement the oclif hook for the deploy commands, you must implement a `project:findDeployers` hook in your project. To do this you need to specify the location of the hook in your package.json and implement the hook. For example, in your package.json - -```json -"oclif": { - "hooks": { - "project:findDeployers": "./lib/hooks/findDeployers" - } -} -``` - -And then in your code, you'll have a `findDeployers.ts` file under the `hooks` directory. Here's an example of a very basic implementation: - -```typescript -// hooks/findDeployers.ts -import { Deployer, Options } from '@salesforce/plugin-deploy-retrieve-utils'; - -const hook = async function (options: Options): Promise { - return []; // return your Deployers here -}; - -export default hook; -``` - -### Deployers - -The [Deployer class](https://github.com/salesforcecli/plugin-deploy-retrieve-utils/blob/main/src/deployer.ts) is a simple interface for setting up and executing deploys. It has two primary methods, `setup` and `deploy`. - -The `setup` method allows developers to do any setup that's required before a deploy can be executed. This could be anything - in some cases you might need to prompt the user for additional information or in other cases you might need to do some environment setup. - -The `deploy` method is where you will write the code that executes the deploy. Again, developers have full autonomy over how this is implemented. diff --git a/README.md b/README.md index bc76ae0e8..d2ef23eed 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,6 @@ sf plugins:install plugin-deploy-retrieve@x.y.z 8. Sign CLA (see [CLA](#cla) below). 9. Send us a pull request when you are done. We'll review your code, suggest any needed changes, and merge it in. -To add a new project command see the [contributing guide](CONTRIBUTING.md) - ### CLA External contributors will be required to sign a Contributor's License diff --git a/command-snapshot.json b/command-snapshot.json index 11775805f..fe02daf24 100644 --- a/command-snapshot.json +++ b/command-snapshot.json @@ -1,23 +1,17 @@ [ { - "command": "deploy", - "plugin": "@salesforce/plugin-deploy-retrieve", - "flags": ["interactive"], - "alias": [], - "flagChars": [], - "flagAliases": [] - }, - { - "command": "project:convert:mdapi", - "plugin": "@salesforce/plugin-deploy-retrieve", - "flags": ["api-version", "json", "loglevel", "manifest", "metadata", "metadata-dir", "output-dir", "root-dir"], "alias": ["force:mdapi:convert"], + "command": "project:convert:mdapi", + "flagAliases": ["apiversion", "metadatapath", "outputdir", "rootdir"], "flagChars": ["d", "m", "p", "r", "x"], - "flagAliases": ["apiversion", "metadatapath", "outputdir", "rootdir"] + "flags": ["api-version", "json", "loglevel", "manifest", "metadata", "metadata-dir", "output-dir", "root-dir"], + "plugin": "@salesforce/plugin-deploy-retrieve" }, { + "alias": ["force:source:convert"], "command": "project:convert:source", - "plugin": "@salesforce/plugin-deploy-retrieve", + "flagAliases": ["apiversion", "outputdir", "packagename", "rootdir", "sourcepath"], + "flagChars": ["d", "m", "n", "p", "r", "x"], "flags": [ "api-version", "json", @@ -29,13 +23,23 @@ "root-dir", "source-dir" ], - "alias": ["force:source:convert"], - "flagChars": ["d", "m", "n", "p", "r", "x"], - "flagAliases": ["apiversion", "outputdir", "packagename", "rootdir", "sourcepath"] + "plugin": "@salesforce/plugin-deploy-retrieve" }, { + "alias": ["force:source:delete"], "command": "project:delete:source", - "plugin": "@salesforce/plugin-deploy-retrieve", + "flagAliases": [ + "apiversion", + "checkonly", + "forceoverwrite", + "noprompt", + "sourcepath", + "targetusername", + "testlevel", + "tracksource", + "u" + ], + "flagChars": ["c", "f", "l", "m", "o", "p", "r", "t", "w"], "flags": [ "api-version", "check-only", @@ -52,63 +56,53 @@ "verbose", "wait" ], - "alias": ["force:source:delete"], - "flagChars": ["c", "f", "l", "m", "o", "p", "r", "t", "w"], - "flagAliases": [ - "apiversion", - "checkonly", - "forceoverwrite", - "noprompt", - "sourcepath", - "targetusername", - "testlevel", - "tracksource", - "u" - ] + "plugin": "@salesforce/plugin-deploy-retrieve" }, { - "command": "project:delete:tracking", - "plugin": "@salesforce/plugin-deploy-retrieve", - "flags": ["api-version", "json", "loglevel", "no-prompt", "target-org"], "alias": ["force:source:tracking:clear"], + "command": "project:delete:tracking", + "flagAliases": ["apiversion", "noprompt", "targetusername", "u"], "flagChars": ["o", "p"], - "flagAliases": ["apiversion", "noprompt", "targetusername", "u"] + "flags": ["api-version", "json", "loglevel", "no-prompt", "target-org"], + "plugin": "@salesforce/plugin-deploy-retrieve" }, { - "command": "project:deploy:cancel", - "plugin": "@salesforce/plugin-deploy-retrieve", - "flags": ["async", "job-id", "json", "use-most-recent", "wait"], "alias": ["deploy:metadata:cancel"], + "command": "project:deploy:cancel", + "flagAliases": [], "flagChars": ["i", "r", "w"], - "flagAliases": [] + "flags": ["async", "job-id", "json", "use-most-recent", "wait"], + "plugin": "@salesforce/plugin-deploy-retrieve" }, { - "command": "project:deploy:preview", - "plugin": "@salesforce/plugin-deploy-retrieve", - "flags": ["ignore-conflicts", "json", "manifest", "metadata", "source-dir", "target-org"], "alias": ["deploy:metadata:preview"], + "command": "project:deploy:preview", + "flagAliases": [], "flagChars": ["c", "d", "m", "o", "x"], - "flagAliases": [] + "flags": ["ignore-conflicts", "json", "manifest", "metadata", "source-dir", "target-org"], + "plugin": "@salesforce/plugin-deploy-retrieve" }, { - "command": "project:deploy:quick", - "plugin": "@salesforce/plugin-deploy-retrieve", - "flags": ["api-version", "async", "concise", "job-id", "json", "target-org", "use-most-recent", "verbose", "wait"], "alias": ["deploy:metadata:quick"], + "command": "project:deploy:quick", + "flagAliases": [], "flagChars": ["a", "i", "o", "r", "w"], - "flagAliases": [] + "flags": ["api-version", "async", "concise", "job-id", "json", "target-org", "use-most-recent", "verbose", "wait"], + "plugin": "@salesforce/plugin-deploy-retrieve" }, { - "command": "project:deploy:report", - "plugin": "@salesforce/plugin-deploy-retrieve", - "flags": ["coverage-formatters", "job-id", "json", "junit", "results-dir", "target-org", "use-most-recent", "wait"], "alias": ["deploy:metadata:report"], + "command": "project:deploy:report", + "flagAliases": [], "flagChars": ["i", "o", "r", "w"], - "flagAliases": [] + "flags": ["coverage-formatters", "job-id", "json", "junit", "results-dir", "target-org", "use-most-recent", "wait"], + "plugin": "@salesforce/plugin-deploy-retrieve" }, { + "alias": ["deploy:metadata:resume"], "command": "project:deploy:resume", - "plugin": "@salesforce/plugin-deploy-retrieve", + "flagAliases": [], + "flagChars": ["i", "r", "w"], "flags": [ "concise", "coverage-formatters", @@ -120,13 +114,13 @@ "verbose", "wait" ], - "alias": ["deploy:metadata:resume"], - "flagChars": ["i", "r", "w"], - "flagAliases": [] + "plugin": "@salesforce/plugin-deploy-retrieve" }, { + "alias": ["deploy:metadata"], "command": "project:deploy:start", - "plugin": "@salesforce/plugin-deploy-retrieve", + "flagAliases": [], + "flagChars": ["a", "c", "d", "g", "l", "m", "o", "r", "t", "w", "x"], "flags": [ "api-version", "async", @@ -153,13 +147,13 @@ "verbose", "wait" ], - "alias": ["deploy:metadata"], - "flagChars": ["a", "c", "d", "g", "l", "m", "o", "r", "t", "w", "x"], - "flagAliases": [] + "plugin": "@salesforce/plugin-deploy-retrieve" }, { + "alias": ["deploy:metadata:validate"], "command": "project:deploy:validate", - "plugin": "@salesforce/plugin-deploy-retrieve", + "flagAliases": [], + "flagChars": ["a", "d", "g", "l", "m", "o", "t", "w", "x"], "flags": [ "api-version", "async", @@ -183,13 +177,22 @@ "verbose", "wait" ], - "alias": ["deploy:metadata:validate"], - "flagChars": ["a", "d", "g", "l", "m", "o", "t", "w", "x"], - "flagAliases": [] + "plugin": "@salesforce/plugin-deploy-retrieve" }, { + "alias": ["force:source:manifest:create"], "command": "project:generate:manifest", - "plugin": "@salesforce/plugin-deploy-retrieve", + "flagAliases": [ + "apiversion", + "fromorg", + "includepackages", + "manifestname", + "manifesttype", + "o", + "outputdir", + "sourcepath" + ], + "flagChars": ["c", "d", "m", "n", "p", "t"], "flags": [ "api-version", "from-org", @@ -202,46 +205,37 @@ "source-dir", "type" ], - "alias": ["force:source:manifest:create"], - "flagChars": ["c", "d", "m", "n", "p", "t"], - "flagAliases": [ - "apiversion", - "fromorg", - "includepackages", - "manifestname", - "manifesttype", - "o", - "outputdir", - "sourcepath" - ] + "plugin": "@salesforce/plugin-deploy-retrieve" }, { - "command": "project:list:ignored", - "plugin": "@salesforce/plugin-deploy-retrieve", - "flags": ["json", "source-dir"], "alias": ["force:source:ignored:list"], + "command": "project:list:ignored", + "flagAliases": ["sourcepath"], "flagChars": ["p"], - "flagAliases": ["sourcepath"] + "flags": ["json", "source-dir"], + "plugin": "@salesforce/plugin-deploy-retrieve" }, { - "command": "project:reset:tracking", - "plugin": "@salesforce/plugin-deploy-retrieve", - "flags": ["api-version", "json", "loglevel", "no-prompt", "revision", "target-org"], "alias": ["force:source:tracking:reset"], + "command": "project:reset:tracking", + "flagAliases": ["apiversion", "noprompt", "targetusername", "u"], "flagChars": ["o", "p", "r"], - "flagAliases": ["apiversion", "noprompt", "targetusername", "u"] + "flags": ["api-version", "json", "loglevel", "no-prompt", "revision", "target-org"], + "plugin": "@salesforce/plugin-deploy-retrieve" }, { - "command": "project:retrieve:preview", - "plugin": "@salesforce/plugin-deploy-retrieve", - "flags": ["ignore-conflicts", "json", "target-org", "concise"], "alias": ["retrieve:metadata:preview"], + "command": "project:retrieve:preview", + "flagAliases": [], "flagChars": ["c", "o"], - "flagAliases": [] + "flags": ["concise", "ignore-conflicts", "json", "target-org"], + "plugin": "@salesforce/plugin-deploy-retrieve" }, { + "alias": ["retrieve:metadata"], "command": "project:retrieve:start", - "plugin": "@salesforce/plugin-deploy-retrieve", + "flagAliases": [], + "flagChars": ["a", "c", "d", "m", "n", "o", "r", "t", "w", "x", "z"], "flags": [ "api-version", "ignore-conflicts", @@ -258,8 +252,6 @@ "wait", "zip-file-name" ], - "alias": ["retrieve:metadata"], - "flagChars": ["a", "c", "d", "m", "n", "o", "r", "t", "w", "x", "z"], - "flagAliases": [] + "plugin": "@salesforce/plugin-deploy-retrieve" } ] diff --git a/messages/deploy.md b/messages/deploy.md deleted file mode 100644 index d04cd31bc..000000000 --- a/messages/deploy.md +++ /dev/null @@ -1,90 +0,0 @@ -# summary - -Deploy a project interactively to any Salesforce environment. - -# description - -This command must be run from within a project. - -The command first analyzes your project, your active or logged-into environments, and local defaults to determine what to deploy and where to deploy it. The command then prompts you for information about this particular deployment and provides intelligent choices based on its analysis. - -For example, if your local project contains a source directory with metadata files in source format, the command asks if you want to deploy that Salesforce app to an org. The command lists your connected orgs and asks which one you want to deploy to. The list of orgs starts with scratch orgs, ordered by expiration date with the most recently created one first, and then Dev Hub and production orgs ordered by name. If the command finds Apex tests, it asks if you want to run them and at which level. - -The command stores your responses in the "deploy-options.json" file in your local project directory and uses them as defaults when you rerun the command. Specify --interactive to force the command to reprompt. - -Use this command for quick and simple deploys. For more complicated deployments, use the environment-specific commands, such as "<%= config.bin %> project deploy start", that provide additional flags. - -# examples - -- Deploy a project and use stored values from a previous command run: - - <%= config.bin %> <%= command.id %> - -- Reprompt for all deployment inputs: - - <%= config.bin %> <%= command.id %> --interactive - -# flags.interactive.summary - -Force the CLI to prompt for all deployment inputs. - -# errors.NoOrgsToSelect - -Can't find any active scratch orgs, Dev Hubs, or other orgs. -Either log into an org or create a scratch org, and then try again. - -# error.initialization - -One or more initialization steps failed. - -# error.initialization.title - -Initialization Failures. The following table describes each failure: - -# error.UserTerminatedDeployForExpiredOrg - -The target-org found in the configuration is expired. The user terminated the deploy. - -# warning.TargetOrgIsExpired - -The target-org, "%s", is expired. Do you want to pick another org? - -# AnalyzingProject - -Analyzing project - -# UsingOptionsFromFile - -Using options found in %s. - -# FoundNothingToDeploy - -Found nothing in the project to deploy. - -# NothingSelectedToDeploy - -Nothing was selected to deploy. - -# DeployOptionsSavedToFile - -Your deploy options have been saved to %s. - -# DeployOptionsIncludedInGitIgnore - -We added %s to the .gitignore for you. - -# DeployersHaveNonZeroExitCode - -One or more of the selected deployers exited with a non-zero exit code. - -# DeployerExitCode - -Deployer "%s" exited with code %d. - -# save.as.default - -Save username %s as default? - -# deprecation - -The top-level deploy command is deprecated. You should use `functions deploy` to deploy functions, and use `project deploy start` to deploy metadata to Salesforce orgs. diff --git a/package.json b/package.json index 4c8665f0d..0bf421461 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@salesforce/plugin-deploy-retrieve", "description": "deploy and retrieve commands for sf", - "version": "2.2.13", + "version": "3.0.0", "author": "Salesforce", "bugs": "https://github.com/forcedotcom/cli/issues", "dependencies": { @@ -56,9 +56,6 @@ "commands": "./lib/commands", "topicSeparator": " ", "bin": "sf", - "hooks": { - "sf:deploy": "./lib/hooks/deploy" - }, "configMeta": "./lib/configMeta", "devPlugins": [ "@oclif/plugin-command-snapshot", diff --git a/schemas/hooks/sf-deploy.json b/schemas/hooks/sf-deploy.json deleted file mode 100644 index 2dc5fb683..000000000 --- a/schemas/hooks/sf-deploy.json +++ /dev/null @@ -1,129 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/MetadataDeployer", - "definitions": { - "MetadataDeployer": { - "type": "object", - "properties": { - "deployables": { - "type": "array", - "items": { - "$ref": "#/definitions/DeployablePackage" - }, - "description": "Deployables are individual pieces that can be deployed on their own. For example, each package in a salesforce project is considered a deployable that can be deployed on its own." - } - }, - "required": ["deployables"], - "additionalProperties": false - }, - "DeployablePackage": { - "type": "object", - "properties": { - "pkg": { - "$ref": "#/definitions/NamedPackageDir" - } - }, - "required": ["pkg"], - "additionalProperties": false - }, - "NamedPackageDir": { - "type": "object", - "additionalProperties": false, - "properties": { - "name": { - "type": "string", - "description": "The [normalized](https://nodejs.org/api/path.html#path_path_normalize_path) path used as the package name." - }, - "fullPath": { - "type": "string", - "description": "The absolute path of the package." - }, - "ancestorId": { - "type": "string" - }, - "ancestorVersion": { - "type": "string" - }, - "default": { - "type": "boolean" - }, - "definitionFile": { - "type": "string" - }, - "dependencies": { - "type": "array", - "items": { - "$ref": "#/definitions/PackageDirDependency" - } - }, - "includeProfileUserLicenses": { - "type": "boolean" - }, - "package": { - "type": "string" - }, - "path": { - "type": "string" - }, - "postInstallScript": { - "type": "string" - }, - "postInstallUrl": { - "type": "string" - }, - "releaseNotesUrl": { - "type": "string" - }, - "scopeProfiles": { - "type": "boolean" - }, - "uninstallScript": { - "type": "string" - }, - "versionDescription": { - "type": "string" - }, - "versionName": { - "type": "string" - }, - "versionNumber": { - "type": "string" - }, - "unpackagedMetadata": { - "type": "object", - "properties": { - "path": { - "type": "string" - } - }, - "required": ["path"], - "additionalProperties": false - }, - "seedMetadata": { - "type": "object", - "properties": { - "path": { - "type": "string" - } - }, - "required": ["path"], - "additionalProperties": false - } - }, - "required": ["fullPath", "name", "path"] - }, - "PackageDirDependency": { - "type": "object", - "properties": { - "package": { - "type": "string" - }, - "versionNumber": { - "type": "string" - } - }, - "required": ["package"], - "additionalProperties": {} - } - } -} diff --git a/src/commands/deploy.ts b/src/commands/deploy.ts deleted file mode 100644 index 32257a88e..000000000 --- a/src/commands/deploy.ts +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright (c) 2021, salesforce.com, inc. - * All rights reserved. - * Licensed under the BSD 3-Clause license. - * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ -/* eslint-disable class-methods-use-this */ - -import { EOL } from 'node:os'; -import { writeFile, readFile } from 'node:fs/promises'; -import { existsSync } from 'node:fs'; -import { exec } from 'node:child_process'; - - -import { Hook } from '@oclif/core'; -import { Messages } from '@salesforce/core'; -import { Env, parseJsonMap } from '@salesforce/kit'; -import { - Deployable, - Deployer, - generateTableChoices, - Prompter, - SfCommand, - SfHook, - Flags, -} from '@salesforce/sf-plugins-core'; -import { DeployerResult } from '@salesforce/sf-plugins-core/lib/deployer.js'; - -Messages.importMessagesDirectoryFromMetaUrl(import.meta.url) - -const messages = Messages.loadMessages('@salesforce/plugin-deploy-retrieve', 'deploy'); - -export const DEPLOY_OPTIONS_FILE = 'deploy-options.json'; - -export default class Deploy extends SfCommand { - public static readonly summary = messages.getMessage('summary'); - public static readonly description = messages.getMessage('description'); - public static readonly examples = messages.getMessages('examples'); - public static enableJsonFlag = false; - public static state = 'deprecated'; - public static readonly hidden = true; - public static deprecationOptions = { - version: '59.0', - message: messages.getMessage('deprecation'), - }; - - public static readonly flags = { - interactive: Flags.boolean({ - summary: messages.getMessage('flags.interactive.summary'), - }), - }; - - public async run(): Promise { - process.setMaxListeners(new Env().getNumber('SF_MAX_EVENT_LISTENERS') ?? 1000); - const { flags } = await this.parse(Deploy); - - flags.interactive = await this.isInteractive(flags.interactive); - const options = await this.readOptions(); - - this.log(messages.getMessage('AnalyzingProject')); - - if (!flags.interactive) { - this.log(messages.getMessage('UsingOptionsFromFile', [DEPLOY_OPTIONS_FILE])); - } - - const hookResults = await SfHook.run(this.config, 'sf:deploy', options); - - this.checkForHookFailures(hookResults); - - let deployers = hookResults.successes.flatMap((s) => s.result); - - if (deployers.length === 0) { - this.log(messages.getMessage('FoundNothingToDeploy')); - } else { - if (flags.interactive) { - deployers = await this.selectDeployers(deployers); - } else { - deployers = deployers.filter((d) => !!options[d.getName()]); - } - - if (deployers.length === 0) { - this.log(messages.getMessage('NothingSelectedToDeploy')); - } - - const deployOptions: Record = {}; - for (const deployer of deployers) { - const opts = options[deployer.getName()] ?? {}; - // setup must be done sequentially - // eslint-disable-next-line no-await-in-loop - deployOptions[deployer.getName()] = await deployer.setup(flags, opts); - } - - if (flags.interactive && (await this.askToSave())) { - await writeJson(DEPLOY_OPTIONS_FILE, deployOptions); - this.log(); - this.log(messages.getMessage('DeployOptionsSavedToFile', [DEPLOY_OPTIONS_FILE])); - if (await this.shouldCommit()) { - await this.commit(); - this.log(messages.getMessage('DeployOptionsIncludedInGitIgnore', [DEPLOY_OPTIONS_FILE])); - } - } - - const deployResults: Array<[Deployer, void | DeployerResult]> = []; - for (const deployer of deployers) { - // deployments must be done sequentially? - // eslint-disable-next-line no-await-in-loop - deployResults.push([deployer, await deployer.deploy()]); - } - if (deployResults.some(([, result]) => !!result && result.exitCode !== 0)) { - process.exitCode = 1; - this.warn(messages.getMessage('DeployersHaveNonZeroExitCode')); - deployResults - .filter(([, result]) => !!result && result.exitCode !== 0) - .forEach(([deployer, result]) => { - this.log( - messages.getMessage('DeployerExitCode', [deployer.getName(), result ? result.exitCode : 'unknown']) - ); - }); - } - } - } - - /** - * If the deploy file exists, we do not want the command to be interactive. But if the file - * does not exist then we want to force the command to be interactive. - */ - // this used to be async when it was using fs-extra. Presered public api - // eslint-disable-next-line @typescript-eslint/require-await - public async isInteractive(interactive: boolean): Promise { - if (interactive) return true; - const deployFileExists = existsSync(DEPLOY_OPTIONS_FILE); - return deployFileExists ? false : true; - } - - public async readOptions(): Promise> { - if (existsSync(DEPLOY_OPTIONS_FILE)) { - return parseJsonMap>(await readFile(DEPLOY_OPTIONS_FILE, 'utf8')); - } else { - return {}; - } - } - - public async commit(): Promise { - const gitignore = await readFile('.gitignore', 'utf-8'); - if (!gitignore.includes(DEPLOY_OPTIONS_FILE)) { - const addition = `${EOL}${EOL}# Deploy Options${EOL}${DEPLOY_OPTIONS_FILE}${EOL}`; - await writeFile('.gitignore', `${gitignore}${addition}`); - } - exec('git add .gitignore'); - exec(`git commit -am "Add ${DEPLOY_OPTIONS_FILE} to .gitignore"`); - } - - // this used to be async when it was using fs-extra. Presered public api - // eslint-disable-next-line @typescript-eslint/require-await - public async shouldCommit(): Promise { - return existsSync('.git') && existsSync('functions'); - } - - public async askToSave(): Promise { - const prompter = new Prompter(); - const { save } = await prompter.prompt<{ save: boolean }>({ - name: 'save', - message: 'Would you like to save these deploy options for future runs?', - type: 'confirm', - }); - return save; - } - - public async selectDeployers(deployers: Deployer[]): Promise { - const deployables: Deployable[] = deployers.reduce((x, y) => x.concat(y.deployables), []); - const columns = { name: 'APP OR PACKAGE', type: 'TYPE', path: 'PATH' }; - const options = deployables.map((deployable) => ({ - name: deployable.getName(), - type: deployable.getType(), - path: deployable.getPath(), - value: deployable, - })); - const prompter = new Prompter(); - const responses = await prompter.prompt<{ deployables: Deployable[] }>([ - { - name: 'deployables', - message: 'Select apps and packages to deploy:', - type: 'checkbox', - choices: generateTableChoices(columns, options), - }, - ]); - - const chosenDeployers: Map = new Map(); - for (const deployable of responses.deployables) { - const parent = deployable.getParent(); - if (chosenDeployers.has(parent)) { - const existing = chosenDeployers.get(parent) ?? []; - chosenDeployers.set(parent, [...existing, deployable]); - } else { - chosenDeployers.set(parent, [deployable]); - } - } - - const final: Deployer[] = []; - for (const [parent, children] of Array.from(chosenDeployers.entries())) { - parent.selectDeployables(children); - final.push(parent); - } - return final; - } - - public checkForHookFailures(hookResults: Hook.Result): void { - if (hookResults.failures?.length) { - // display a table of the errors encountered; Plugin Name, Error Message - const columns = { - errorName: { header: 'Error Name' }, - errorMessage: { header: 'Error Message' }, - }; - - const failureData = hookResults.failures.map((failure) => ({ - errorName: failure.error.name, - errorMessage: failure.error.message, - })); - this.styledHeader(messages.getMessage('error.initialization.title')); - this.table(failureData, columns, { 'no-truncate': true }); - const err = messages.createError('error.initialization'); - err.data = hookResults.failures; - throw err; - } - } -} - -export const writeJson = async (filename: string, data: Record): Promise => - writeFile(filename, JSON.stringify(data, null, 2)); diff --git a/src/configMeta.ts b/src/configMeta.ts index 823e24903..3c18b8f9e 100644 --- a/src/configMeta.ts +++ b/src/configMeta.ts @@ -5,12 +5,10 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ - - import type { ConfigValue } from '@salesforce/core'; import { Messages } from '@salesforce/core/lib/messages.js'; -Messages.importMessagesDirectoryFromMetaUrl(import.meta.url) +Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); const messages = Messages.loadMessages('@salesforce/plugin-deploy-retrieve', 'config'); export enum ConfigVars { diff --git a/src/formatters/asyncDeployCancelResultFormatter.ts b/src/formatters/asyncDeployCancelResultFormatter.ts index 409d1b42e..acaad39f0 100644 --- a/src/formatters/asyncDeployCancelResultFormatter.ts +++ b/src/formatters/asyncDeployCancelResultFormatter.ts @@ -5,13 +5,9 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ - import { ux } from '@oclif/core'; import { Messages } from '@salesforce/core'; import { AsyncDeployResultJson, DeployResultJson, Formatter } from '../utils/types.js'; -Messages.importMessagesDirectoryFromMetaUrl(import.meta.url) - -export const deployAsyncMessages = Messages.loadMessages('@salesforce/plugin-deploy-retrieve', 'deploy.async'); export class AsyncDeployCancelResultFormatter implements Formatter { public constructor(private id: string, private bin: string) {} @@ -22,6 +18,9 @@ export class AsyncDeployCancelResultFormatter implements Formatter { public constructor(private id: string, private bin: string) {} @@ -21,6 +18,9 @@ export class AsyncDeployResultFormatter implements Formatter = async function () { - const project = await SfProject.resolve(); - const packageDirectories = project.getPackageDirectories(); - return [new MetadataDeployer(packageDirectories)]; -}; - -export default hook; diff --git a/src/utils/coverage.ts b/src/utils/coverage.ts index 2946054db..bdbb30bdb 100644 --- a/src/utils/coverage.ts +++ b/src/utils/coverage.ts @@ -14,7 +14,7 @@ import { ApexCodeCoverageAggregate, ApexCodeCoverageAggregateRecord, } from '@salesforce/apex-node'; -import { Successes, Failures, CodeCoverage } from '@salesforce/source-deploy-retrieve'; +import type { Successes, Failures, CodeCoverage } from '@salesforce/source-deploy-retrieve'; import { ensureArray } from '@salesforce/kit'; import { StandardColors } from '@salesforce/sf-plugins-core'; diff --git a/src/utils/flags.ts b/src/utils/flags.ts index 1537f3e3a..8734c3a1a 100644 --- a/src/utils/flags.ts +++ b/src/utils/flags.ts @@ -11,7 +11,7 @@ import { Flags } from '@oclif/core'; import { Messages, Lifecycle } from '@salesforce/core'; import { PathInfo, TestLevel, reportsFormatters } from './types.js'; -Messages.importMessagesDirectoryFromMetaUrl(import.meta.url) +Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); const messages = Messages.loadMessages('@salesforce/plugin-deploy-retrieve', 'validation'); const commonFlagMessages = Messages.loadMessages('@salesforce/plugin-deploy-retrieve', 'commonFlags'); @@ -109,12 +109,13 @@ export const coverageFormattersFlag = Flags.custom({ description: commonFlagMessages.getMessage('flags.coverage-formatters.description'), options: reportsFormatters, }); + /** * use when the old version allowed comma separated values, and the change is confusing enough to deserve a warning * Put this as the parse function, like the testsFlag above * */ -export const commaWarningForMultipleFlags = async (input: string, warningText: string): Promise => { +const commaWarningForMultipleFlags = async (input: string, warningText: string): Promise => { if (input.includes(',')) { await Lifecycle.getInstance().emitWarning(warningText); } diff --git a/src/utils/metadataDeployer.ts b/src/utils/metadataDeployer.ts deleted file mode 100644 index bbfbebe58..000000000 --- a/src/utils/metadataDeployer.ts +++ /dev/null @@ -1,265 +0,0 @@ -/* - * Copyright (c) 2021, salesforce.com, inc. - * All rights reserved. - * Licensed under the BSD 3-Clause license. - * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ -/* eslint-disable class-methods-use-this */ - - - -import { EOL } from 'node:os'; -import chalk from 'chalk'; -import { Duration } from '@salesforce/kit'; -import { - AuthInfo, - ConfigAggregator, - StateAggregator, - Messages, - NamedPackageDir, - OrgAuthorization, - OrgConfigProperties, - Org, -} from '@salesforce/core'; -import { Deployable, Deployer, DeployerResult, generateTableChoices } from '@salesforce/sf-plugins-core'; - -import { DeployResultFormatter } from '../formatters/deployResultFormatter.js'; -import { TestLevel } from './types.js'; -import { DeployProgress } from './progressBar.js'; -import { determineExitCode, executeDeploy, resolveApi } from './deploy.js'; - -Messages.importMessagesDirectoryFromMetaUrl(import.meta.url) -const messages = Messages.loadMessages('@salesforce/plugin-deploy-retrieve', 'deploy'); - -type OrgAuthWithTimestamp = OrgAuthorization & { timestamp: Date }; - -const compareOrgs = (a: OrgAuthWithTimestamp, b: OrgAuthWithTimestamp): number => { - // scratch orgs before other orgs - if (a.isScratchOrg && !b.isScratchOrg) { - // all scratch orgs come before non-scratch orgs - return -1; - } else { - // sort scratch orgs by timestamp - descending - if (a.isScratchOrg && b.isScratchOrg) { - const aTimestamp = new Date(a.timestamp); - const bTimestamp = new Date(b.timestamp); - return bTimestamp.getTime() - aTimestamp.getTime(); - } - // dev hubs after scratch but before remaining orgs - if (a.isDevHub && !b.isScratchOrg && !b.isDevHub) { - return -1; - } - // not a scratch org and not a devhub means "other" sorts last - if (!a.isDevHub) { - return 1; - } - } - // orgs are equal by type - sort by name ascending - return a.username.localeCompare(b.username); -}; - -export interface MetadataDeployOptions extends Deployer.Options { - testLevel?: TestLevel; - username?: string; - directories?: string[]; -} - -export class DeployablePackage extends Deployable { - public constructor(public pkg: NamedPackageDir, private parent: Deployer) { - super(); - } - - public getName(): string { - return this.pkg.name; - } - - public getType(): string { - return 'Salesforce App'; - } - - public getPath(): string { - return this.pkg.path; - } - - public getParent(): Deployer { - return this.parent; - } -} - -export class MetadataDeployer extends Deployer { - public static NAME = 'Salesforce Apps'; - - public declare deployables: DeployablePackage[]; - private testLevel: TestLevel = TestLevel.NoTestRun; - private username!: string; - - public constructor(private packages: NamedPackageDir[]) { - super(); - this.deployables = this.packages.map((pkg) => new DeployablePackage(pkg, this)); - } - - public getName(): string { - return MetadataDeployer.NAME; - } - - public async setup(flags: Deployer.Flags, options: MetadataDeployOptions): Promise { - if (flags.interactive) { - this.testLevel = await this.promptForTestLevel(); - this.username = await this.promptForUsername(); - } else { - if (options.directories?.length) { - const directories = options.directories || []; - const selected = this.deployables.filter((d) => directories.includes(d.getPath())); - this.selectDeployables(selected); - } - this.testLevel = options.testLevel ?? (await this.promptForTestLevel()); - this.username = options.username ?? (await this.promptForUsername()); - } - - return { - testLevel: this.testLevel, - username: this.username, - apps: this.deployables.map((d) => d.getPath()), - }; - } - - public async deploy(): Promise { - const directories = this.deployables.map((d) => d.pkg.fullPath); - const name = this.deployables.map((p) => chalk.cyan.bold(p.getPath())).join(', '); - const api = await resolveApi(); - this.log(`${EOL}Deploying ${name} to ${this.username} using ${api} API`); - - const { deploy } = await executeDeploy( - { - 'target-org': this.username, - 'source-dir': directories, - 'test-level': this.testLevel, - api, - }, - undefined, - undefined, - undefined, - true - ); - - new DeployProgress(deploy).start(); - - const result = await deploy.pollStatus(500, Duration.minutes(33).seconds); - - const formatter = new DeployResultFormatter(result, { - 'test-level': this.testLevel, - verbose: false, - concise: false, - 'target-org': await Org.create({ aliasOrUsername: this.username }), - }); - formatter.display(); - const deployerResult: DeployerResult = { - exitCode: determineExitCode(result), - }; - return deployerResult as R; - } - - public async promptForUsername(): Promise { - const aliasOrUsername = ConfigAggregator.getValue(OrgConfigProperties.TARGET_ORG)?.value as string; - const stateAggregator = await StateAggregator.getInstance(); - await stateAggregator.orgs.readAll(); - const allAliases = stateAggregator.aliases.getAll(); - let targetOrgAuth: OrgAuthorization | undefined; - // make sure the "target-org" can be used in this deploy - if (aliasOrUsername) { - targetOrgAuth = ( - await AuthInfo.listAllAuthorizations( - (a) => (a.username === aliasOrUsername || a.aliases?.some((alias) => alias === aliasOrUsername)) ?? false - ) - ).find((a) => a); - if (targetOrgAuth) { - if (targetOrgAuth?.isExpired) { - const continueAnswer = await this.prompt<{ continue: boolean }>([ - { - name: 'continue', - type: 'confirm', - message: chalk.red(messages.getMessage('warning.TargetOrgIsExpired', [aliasOrUsername])), - }, - ]); - if (!continueAnswer.continue) { - throw messages.createError('error.UserTerminatedDeployForExpiredOrg'); - } - } else { - return stateAggregator.aliases.resolveUsername(aliasOrUsername); - } - } - } - - if (!aliasOrUsername || targetOrgAuth?.isExpired) { - const promises = ( - await AuthInfo.listAllAuthorizations((orgAuth) => !orgAuth.error && orgAuth.isExpired !== true) - ).map(async (orgAuth) => { - const stat = await stateAggregator.orgs.stat(orgAuth.username); - const timestamp = stat ? new Date(stat.mtimeMs) : new Date(); - return { ...orgAuth, timestamp }; - }); - - const authorizations = await Promise.all(promises); - if (authorizations.length > 0) { - const newestAuths = authorizations.sort(compareOrgs); - const options = newestAuths.map((auth) => ({ - name: auth.username, - aliases: Object.entries(allAliases) - .filter(([, usernameOrAlias]) => usernameOrAlias === auth.username) - .map(([alias]) => alias) - .join(', '), - isScratchOrg: auth.isScratchOrg ? 'Yes' : 'No', - value: auth.username, - })); - const columns = { name: 'Org', aliases: 'Aliases', isScratchOrg: 'Scratch Org' }; - const { username } = await this.prompt<{ username: string }>([ - { - name: 'username', - message: 'Select the org you want to deploy to:', - type: 'list', - choices: generateTableChoices(columns, options, false), - }, - ]); - if (targetOrgAuth?.isExpired) { - const setTargetOrg = await this.prompt<{ save: boolean }>([ - { - name: 'save', - type: 'confirm', - message: messages.getMessage('save.as.default', [username]), - }, - ]); - if (setTargetOrg.save) { - const authInfo = await AuthInfo.create({ username }); - await authInfo.setAsDefault({ org: true }); - } - } - return username; - } else { - throw messages.createError('errors.NoOrgsToSelect'); - } - } - throw new Error('Unexpected: You should not have arrived here.'); - } - - public async promptForTestLevel(): Promise { - const { testLevel } = await this.prompt<{ testLevel: TestLevel }>([ - { - name: 'testLevel', - message: 'Select the test level you would like to run:', - type: 'list', - loop: false, - pageSize: 4, - choices: [ - { name: "Don't run tests", value: TestLevel.NoTestRun, short: "Don't run tests" }, - { name: 'Run local tests', value: TestLevel.RunLocalTests, short: 'Run local tests' }, - { - name: 'Run all tests in environment', - value: TestLevel.RunAllTestsInOrg, - short: 'Run all tests in environment', - }, - ], - }, - ]); - return testLevel; - } -} diff --git a/src/utils/previewOutput.ts b/src/utils/previewOutput.ts index 0cd50a229..a6d862e61 100644 --- a/src/utils/previewOutput.ts +++ b/src/utils/previewOutput.ts @@ -24,7 +24,7 @@ import { filePathsFromMetadataComponent } from '@salesforce/source-deploy-retrie import { SourceTracking } from '@salesforce/source-tracking'; import { isSourceComponentWithXml } from './types.js'; -Messages.importMessagesDirectoryFromMetaUrl(import.meta.url) +Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); const messages = Messages.loadMessages('@salesforce/plugin-deploy-retrieve', 'previewMessages'); type BaseOperation = 'deploy' | 'retrieve'; @@ -241,7 +241,7 @@ const printConflictsTable = (files: PreviewFile[]): void => { } }; -export const printIgnoredTable = (files: PreviewFile[], baseOperation: BaseOperation): void => { +const printIgnoredTable = (files: PreviewFile[], baseOperation: BaseOperation): void => { ux.log(); if (files.length === 0) { ux.log(chalk.dim(messages.getMessage('ignored.none'))); diff --git a/src/utils/types.ts b/src/utils/types.ts index 0e316ad6a..2fedca92b 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -16,7 +16,7 @@ import { FileResponseSuccess, } from '@salesforce/source-deploy-retrieve'; import { isObject } from '@salesforce/ts-types'; -import { DefaultReportOptions, CoverageReporterOptions } from '@salesforce/apex-node'; +import { DefaultReportOptions } from '@salesforce/apex-node'; export const reportsFormatters = Object.keys(DefaultReportOptions); @@ -57,18 +57,6 @@ export type ConvertResultJson = { location: string; }; -export interface DeleteFormatterOptions { - verbose?: boolean; - quiet?: boolean; - waitTime?: number; - concise?: boolean; - username?: string; - coverageOptions?: CoverageReporterOptions; - junitTestResults?: boolean; - resultsDir?: string; - testsRan?: boolean; -} - export type DeleteSourceJson = { deletedSource?: FileResponse[]; deployedSource: FileResponse[]; diff --git a/test/commands/deploy.nut.ts b/test/commands/deploy.nut.ts deleted file mode 100644 index 8e6ed314c..000000000 --- a/test/commands/deploy.nut.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2020, salesforce.com, inc. - * All rights reserved. - * Licensed under the BSD 3-Clause license. - * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ - -import * as path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { SourceTestkit } from '@salesforce/source-testkit'; -import { writeJson } from '../../src/commands/deploy.js'; -import { TestLevel } from '../../src/utils/types.js'; -import { MetadataDeployer } from '../../src/utils/metadataDeployer.js'; - -describe('deploy NUTs', () => { - let testkit: SourceTestkit; - - before(async () => { - testkit = await SourceTestkit.create({ - repository: 'https://github.com/trailheadapps/dreamhouse-lwc.git', - nut: fileURLToPath(import.meta.url), - }); - }); - - after(async () => { - await testkit?.clean(); - }); - - describe('deploy-options.json', () => { - it('should deploy force-app', async () => { - const deployOptions = { - [MetadataDeployer.NAME]: { - testLevel: TestLevel.NoTestRun, - username: testkit.username, - apps: ['force-app'], - }, - }; - await writeJson(path.join(testkit.projectDir, 'deploy-options.json'), deployOptions); - await testkit.execute('deploy', { json: false }); - await testkit.expect.filesToBeDeployed(['force-app/**/*'], ['force-app/test/**/*']); - }); - }); -}); diff --git a/test/commands/deploy.test.ts b/test/commands/deploy.test.ts deleted file mode 100644 index 5de535594..000000000 --- a/test/commands/deploy.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2020, salesforce.com, inc. - * All rights reserved. - * Licensed under the BSD 3-Clause license. - * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ -/* eslint-disable class-methods-use-this */ - -import { expect } from 'chai'; -import { Hook } from '@oclif/core'; -import { Deployer } from '@salesforce/sf-plugins-core'; -import Deploy from '../../src/commands/deploy.js'; -import Result = Hook.Result; - -class TestDeploy extends Deploy { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - public constructor() { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - super([]); - } - public async run() {} - - public async styledHeader() {} - - public async table() {} -} - -describe('checkForHookFailures', () => { - it('should not throw when no hook failures', () => { - const testDeploy = new TestDeploy(); - testDeploy.checkForHookFailures({} as Result); - expect(testDeploy).to.be.ok; - }); - it('should throw when hook failures are present', () => { - const testDeploy = new TestDeploy(); - const shouldThrow = () => - testDeploy.checkForHookFailures({ - successes: [], - failures: [{ plugin: { name: 'foo' }, error: { name: 'fooerror', message: 'bad stuff happened' } }], - // plugin has a lot more properties this test omits. - } as unknown as Result); - expect(shouldThrow).to.throw(/One or more initialization steps failed./); - }); -}); - -/** - * Conversion of type '{ successes: never[]; failures: { plugin: { name: string; }; error: { name: string; message: string; }; }[]; }' to type 'Result' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. - Types of property 'failures' are incompatible. - Type '{ plugin: { name: string; }; error: { name: string; message: string; }; }[]' is not comparable to type '{ error: Error; plugin: Plugin; }[]'. - Type '{ plugin: { name: string; }; error: { name: string; message: string; }; }' is not comparable to type '{ error: Error; plugin: Plugin; }'. - Types of property 'plugin' are incompatible. - Type '{ name: string; }' is missing the following properties from type 'Plugin': _base, alias, version, pjson, and 9 more. - * - */ diff --git a/test/nuts/tracking/basics.nut.ts b/test/nuts/tracking/basics.nut.ts index eecea14ce..388bbd93f 100644 --- a/test/nuts/tracking/basics.nut.ts +++ b/test/nuts/tracking/basics.nut.ts @@ -10,7 +10,7 @@ import * as fs from 'node:fs'; import { expect, assert } from 'chai'; import { execCmd, TestSession } from '@salesforce/cli-plugins-testkit'; import { ComponentStatus } from '@salesforce/source-deploy-retrieve'; -import { StatusResult } from '@salesforce/plugin-source/lib/formatters/source/statusFormatter.js'; +import type { StatusResult } from '@salesforce/plugin-source/lib/formatters/source/statusFormatter.js'; import { DeployResultJson, RetrieveResultJson, isSdrFailure } from '../../../src/utils/types.js'; import { PreviewResult } from '../../../src/utils/previewOutput.js'; import { eBikesDeployResultCount } from './constants.js'; diff --git a/test/nuts/tracking/conflicts.nut.ts b/test/nuts/tracking/conflicts.nut.ts index 6639fb6ba..76865e71e 100644 --- a/test/nuts/tracking/conflicts.nut.ts +++ b/test/nuts/tracking/conflicts.nut.ts @@ -12,7 +12,7 @@ import { expect } from 'chai'; import { execCmd, TestSession } from '@salesforce/cli-plugins-testkit'; import { AuthInfo, Connection } from '@salesforce/core'; import { ComponentStatus } from '@salesforce/source-deploy-retrieve'; -import { StatusResult } from '@salesforce/plugin-source/lib/formatters/source/statusFormatter.js'; +import type { StatusResult } from '@salesforce/plugin-source/lib/formatters/source/statusFormatter.js'; import { DeployResultJson, RetrieveResultJson } from '../../../src/utils/types.js'; import { PreviewResult } from '../../../src/utils/previewOutput.js'; import { eBikesDeployResultCount } from './constants.js'; diff --git a/test/nuts/tracking/forceIgnore.nut.ts b/test/nuts/tracking/forceIgnore.nut.ts index 70005222d..35e2de16e 100644 --- a/test/nuts/tracking/forceIgnore.nut.ts +++ b/test/nuts/tracking/forceIgnore.nut.ts @@ -12,7 +12,7 @@ import { expect } from 'chai'; import { execCmd, TestSession } from '@salesforce/cli-plugins-testkit'; import { AuthInfo, Connection } from '@salesforce/core'; import { ComponentStatus } from '@salesforce/source-deploy-retrieve'; -import { StatusResult } from '@salesforce/plugin-source/lib/formatters/source/statusFormatter.js'; +import type { StatusResult } from '@salesforce/plugin-source/lib/formatters/source/statusFormatter.js'; import { DeployResultJson, RetrieveResultJson } from '../../../src/utils/types.js'; import { PreviewResult } from '../../../src/utils/previewOutput.js'; diff --git a/test/nuts/tracking/lwc.nut.ts b/test/nuts/tracking/lwc.nut.ts index a525498a0..25d188a1d 100644 --- a/test/nuts/tracking/lwc.nut.ts +++ b/test/nuts/tracking/lwc.nut.ts @@ -9,7 +9,7 @@ import * as path from 'node:path'; import * as fs from 'node:fs'; import { assert, expect } from 'chai'; import { execCmd, TestSession } from '@salesforce/cli-plugins-testkit'; -import { StatusResult } from '@salesforce/plugin-source/lib/formatters/source/statusFormatter.js'; +import type { StatusResult } from '@salesforce/plugin-source/lib/formatters/source/statusFormatter.js'; import { ComponentStatus } from '@salesforce/source-deploy-retrieve'; import { PreviewResult } from '../../../src/utils/previewOutput.js'; import { DeployResultJson } from '../../../src/utils/types.js'; diff --git a/test/nuts/tracking/remoteChanges.nut.ts b/test/nuts/tracking/remoteChanges.nut.ts index 89fa06e85..f47acb45e 100644 --- a/test/nuts/tracking/remoteChanges.nut.ts +++ b/test/nuts/tracking/remoteChanges.nut.ts @@ -12,7 +12,7 @@ import { expect, assert } from 'chai'; import { execCmd, TestSession } from '@salesforce/cli-plugins-testkit'; import { AuthInfo, Connection } from '@salesforce/core'; import { ComponentStatus, FileResponse } from '@salesforce/source-deploy-retrieve'; -import { StatusResult } from '@salesforce/plugin-source/lib/formatters/source/statusFormatter.js'; +import type { StatusResult } from '@salesforce/plugin-source/lib/formatters/source/statusFormatter.js'; import { PreviewResult, PreviewFile } from '../../../src/utils/previewOutput.js'; import { DeployResultJson, RetrieveResultJson } from '../../../src/utils/types.js'; import { eBikesDeployResultCount } from './constants.js';