diff --git a/command-snapshot.json b/command-snapshot.json index 2587b002..1a36268b 100644 --- a/command-snapshot.json +++ b/command-snapshot.json @@ -1,7 +1,9 @@ [ { + "alias": [], "command": "force:org:clone", - "plugin": "@salesforce/plugin-org", + "flagAliases": ["apiversion", "targetusername", "u"], + "flagChars": ["a", "f", "o", "s", "t", "w"], "flags": [ "api-version", "definitionfile", @@ -13,13 +15,13 @@ "type", "wait" ], - "alias": [], - "flagChars": ["a", "f", "o", "s", "t", "w"], - "flagAliases": ["apiversion", "targetusername", "u"] + "plugin": "@salesforce/plugin-org" }, { + "alias": [], "command": "force:org:create", - "plugin": "@salesforce/plugin-org", + "flagAliases": ["apiversion", "targetdevhubusername", "targetusername", "u"], + "flagChars": ["a", "c", "d", "f", "i", "n", "o", "s", "t", "v", "w"], "flags": [ "api-version", "clientid", @@ -37,29 +39,29 @@ "type", "wait" ], - "alias": [], - "flagChars": ["a", "c", "d", "f", "i", "n", "o", "s", "t", "v", "w"], - "flagAliases": ["apiversion", "targetdevhubusername", "targetusername", "u"] + "plugin": "@salesforce/plugin-org" }, { - "command": "force:org:delete", - "plugin": "@salesforce/plugin-org", - "flags": ["api-version", "json", "loglevel", "no-prompt", "target-org", "targetdevhubusername"], "alias": [], + "command": "force:org:delete", + "flagAliases": ["apiversion", "noprompt", "targetusername", "u"], "flagChars": ["o", "p", "v"], - "flagAliases": ["apiversion", "noprompt", "targetusername", "u"] + "flags": ["api-version", "json", "loglevel", "no-prompt", "target-org", "targetdevhubusername"], + "plugin": "@salesforce/plugin-org" }, { - "command": "force:org:status", - "plugin": "@salesforce/plugin-org", - "flags": ["api-version", "json", "loglevel", "sandboxname", "setalias", "setdefaultusername", "target-org", "wait"], "alias": [], + "command": "force:org:status", + "flagAliases": ["apiversion", "targetusername", "u"], "flagChars": ["a", "n", "o", "s", "w"], - "flagAliases": ["apiversion", "targetusername", "u"] + "flags": ["api-version", "json", "loglevel", "sandboxname", "setalias", "setdefaultusername", "target-org", "wait"], + "plugin": "@salesforce/plugin-org" }, { + "alias": ["env:create:sandbox"], "command": "org:create:sandbox", - "plugin": "@salesforce/plugin-org", + "flagAliases": [], + "flagChars": ["a", "c", "f", "i", "l", "n", "o", "s", "w"], "flags": [ "alias", "async", @@ -75,13 +77,13 @@ "target-org", "wait" ], - "alias": ["env:create:sandbox"], - "flagChars": ["a", "c", "f", "i", "l", "n", "o", "s", "w"], - "flagAliases": [] + "plugin": "@salesforce/plugin-org" }, { + "alias": ["env:create:scratch"], "command": "org:create:scratch", - "plugin": "@salesforce/plugin-org", + "flagAliases": [], + "flagChars": ["a", "c", "d", "e", "f", "i", "m", "t", "v", "w", "y"], "flags": [ "admin-email", "alias", @@ -104,80 +106,94 @@ "username", "wait" ], - "alias": ["env:create:scratch"], - "flagChars": ["a", "c", "d", "e", "f", "i", "m", "t", "v", "w", "y"], - "flagAliases": [] + "plugin": "@salesforce/plugin-org" }, { - "command": "org:delete:sandbox", - "plugin": "@salesforce/plugin-org", - "flags": ["json", "no-prompt", "target-org"], "alias": ["env:delete:sandbox"], + "command": "org:delete:sandbox", + "flagAliases": ["targetusername", "u"], "flagChars": ["o", "p"], - "flagAliases": ["targetusername", "u"] + "flags": ["json", "no-prompt", "target-org"], + "plugin": "@salesforce/plugin-org" }, { - "command": "org:delete:scratch", - "plugin": "@salesforce/plugin-org", - "flags": ["json", "no-prompt", "target-org"], "alias": ["env:delete:scratch"], + "command": "org:delete:scratch", + "flagAliases": ["targetusername", "u"], "flagChars": ["o", "p"], - "flagAliases": ["targetusername", "u"] + "flags": ["json", "no-prompt", "target-org"], + "plugin": "@salesforce/plugin-org" + }, + { + "alias": [], + "command": "org:disable:tracking", + "flagAliases": [], + "flagChars": ["o"], + "flags": ["json", "target-org"], + "plugin": "@salesforce/plugin-org" }, { + "alias": ["force:org:display"], "command": "org:display", - "plugin": "@salesforce/plugin-org", + "flagAliases": ["apiversion", "targetusername", "u"], + "flagChars": ["o"], "flags": ["api-version", "json", "loglevel", "target-org", "verbose"], - "alias": ["force:org:display"], + "plugin": "@salesforce/plugin-org" + }, + { + "alias": [], + "command": "org:enable:tracking", + "flagAliases": [], "flagChars": ["o"], - "flagAliases": ["apiversion", "targetusername", "u"] + "flags": ["json", "target-org"], + "plugin": "@salesforce/plugin-org" }, { - "command": "org:list", - "plugin": "@salesforce/plugin-org", - "flags": ["all", "clean", "json", "loglevel", "no-prompt", "skip-connection-status", "verbose"], "alias": ["force:org:list"], + "command": "org:list", + "flagAliases": ["noprompt", "skipconnectionstatus"], "flagChars": ["p"], - "flagAliases": ["noprompt", "skipconnectionstatus"] + "flags": ["all", "clean", "json", "loglevel", "no-prompt", "skip-connection-status", "verbose"], + "plugin": "@salesforce/plugin-org" }, { - "command": "org:list:metadata", - "plugin": "@salesforce/plugin-org", - "flags": ["api-version", "folder", "json", "loglevel", "metadata-type", "output-file", "target-org"], "alias": ["force:mdapi:listmetadata"], + "command": "org:list:metadata", + "flagAliases": ["a", "apiversion", "metadatatype", "resultfile", "targetusername", "u"], "flagChars": ["f", "m", "o"], - "flagAliases": ["a", "apiversion", "metadatatype", "resultfile", "targetusername", "u"] + "flags": ["api-version", "folder", "json", "loglevel", "metadata-type", "output-file", "target-org"], + "plugin": "@salesforce/plugin-org" }, { - "command": "org:list:metadata-types", - "plugin": "@salesforce/plugin-org", - "flags": ["api-version", "filter-known", "json", "loglevel", "output-file", "target-org"], "alias": ["force:mdapi:describemetadata"], + "command": "org:list:metadata-types", + "flagAliases": ["a", "apiversion", "filterknown", "resultfile", "targetusername", "u"], "flagChars": ["f", "k", "o"], - "flagAliases": ["a", "apiversion", "filterknown", "resultfile", "targetusername", "u"] + "flags": ["api-version", "filter-known", "json", "loglevel", "output-file", "target-org"], + "plugin": "@salesforce/plugin-org" }, { - "command": "org:open", - "plugin": "@salesforce/plugin-org", - "flags": ["api-version", "browser", "json", "loglevel", "path", "source-file", "target-org", "url-only"], "alias": ["force:org:open", "force:source:open"], + "command": "org:open", + "flagAliases": ["apiversion", "sourcefile", "targetusername", "u", "urlonly"], "flagChars": ["b", "f", "o", "p", "r"], - "flagAliases": ["apiversion", "sourcefile", "targetusername", "u", "urlonly"] + "flags": ["api-version", "browser", "json", "loglevel", "path", "source-file", "target-org", "url-only"], + "plugin": "@salesforce/plugin-org" }, { - "command": "org:resume:sandbox", - "plugin": "@salesforce/plugin-org", - "flags": ["job-id", "json", "name", "target-org", "use-most-recent", "wait"], "alias": ["env:resume:sandbox"], + "command": "org:resume:sandbox", + "flagAliases": [], "flagChars": ["i", "l", "n", "o", "w"], - "flagAliases": [] + "flags": ["job-id", "json", "name", "target-org", "use-most-recent", "wait"], + "plugin": "@salesforce/plugin-org" }, { - "command": "org:resume:scratch", - "plugin": "@salesforce/plugin-org", - "flags": ["job-id", "json", "use-most-recent"], "alias": ["env:resume:scratch"], + "command": "org:resume:scratch", + "flagAliases": [], "flagChars": ["i", "r"], - "flagAliases": [] + "flags": ["job-id", "json", "use-most-recent"], + "plugin": "@salesforce/plugin-org" } ] diff --git a/messages/org.disable.tracking.md b/messages/org.disable.tracking.md new file mode 100644 index 00000000..089392d7 --- /dev/null +++ b/messages/org.disable.tracking.md @@ -0,0 +1,25 @@ +# summary + +Disable source tracking in local auth file. + +# description + +This has no effect on the org. It stores the setting in the CLI's configuration file for this org so that no source tracking operations are executed when working with this org. + +# examples + +Disable tracking on an org using an alias + +- <%= config.bin %> <%= command.id %> -o someAlias + +Disable tracking on an org using a username + +- <%= config.bin %> <%= command.id %> -o you@example.com + +Disable tracking on your default org + +- <%= config.bin %> <%= command.id %> + +# success + +Disabled source tracking for %s. diff --git a/messages/org.enable.tracking.md b/messages/org.enable.tracking.md new file mode 100644 index 00000000..b79528f7 --- /dev/null +++ b/messages/org.enable.tracking.md @@ -0,0 +1,39 @@ +# summary + +Enable source tracking in local auth file. + +# description + +This has no effect on the org. It stores the setting in the CLI's configuration file for this org so that source tracking operations are executed when working with this org. + +This command will throw an error if the org does not support tracking. + +# examples + +Enable tracking on an org using an alias + +- <%= config.bin %> <%= command.id %> -o someAlias + +Enable tracking on an org using a username + +- <%= config.bin %> <%= command.id %> -o you@example.com + +Enable tracking on your default org + +- <%= config.bin %> <%= command.id %> + +# success + +Enabled source tracking for %s. + +# error.TrackingNotAvailable + +This org cannot enable source tracking because the SourceMember object is not available, or you do not have access to it. + +# error.TrackingNotAvailable.actions + +- If the org is a sandbox, make sure that your production org has Source Tracking enabled in sandboxes. See https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_setup_enable_source_tracking_sandboxes.htm. + +- Make sure that your user can access the SourceMembers table via the tooling API. + +- If the Org is a production org, source tracking cannot be enabled. diff --git a/package.json b/package.json index 9431cb50..b60139ea 100644 --- a/package.json +++ b/package.json @@ -70,8 +70,15 @@ }, "delete": { "description": "Delete scratch orgs, sandboxes, org shapes, and org snapshots." + }, + "enable": { + "description": "Enable tracking on an org." + }, + "disable": { + "description": "Disable tracking on an org" } - } + }, + "external": true }, "force": { "external": true, diff --git a/schemas/org-disable-tracking.json b/schemas/org-disable-tracking.json new file mode 100644 index 00000000..78d7b26d --- /dev/null +++ b/schemas/org-disable-tracking.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/OrgDisableTrackingResult", + "definitions": { + "OrgDisableTrackingResult": { + "type": "object", + "properties": { + "tracksSource": { + "type": "boolean" + }, + "username": { + "type": "string" + } + }, + "required": ["tracksSource", "username"], + "additionalProperties": false + } + } +} diff --git a/schemas/org-enable-tracking.json b/schemas/org-enable-tracking.json new file mode 100644 index 00000000..cbc31e71 --- /dev/null +++ b/schemas/org-enable-tracking.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/OrgEnableTrackingResult", + "definitions": { + "OrgEnableTrackingResult": { + "type": "object", + "properties": { + "tracksSource": { + "type": "boolean" + }, + "username": { + "type": "string" + } + }, + "required": ["tracksSource", "username"], + "additionalProperties": false + } + } +} diff --git a/src/commands/org/disable/tracking.ts b/src/commands/org/disable/tracking.ts new file mode 100644 index 00000000..b1a05181 --- /dev/null +++ b/src/commands/org/disable/tracking.ts @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023, 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 { dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { SfCommand, Flags } from '@salesforce/sf-plugins-core'; +import { Messages } from '@salesforce/core'; + +Messages.importMessagesDirectory(dirname(fileURLToPath(import.meta.url))); +const messages = Messages.loadMessages('@salesforce/plugin-org', 'org.disable.tracking'); + +export type OrgDisableTrackingResult = { + tracksSource: boolean; + username: string; +}; + +export default class OrgDisableTracking 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 readonly flags = { + 'target-org': Flags.requiredOrg(), + }; + + public async run(): Promise { + const { flags } = await this.parse(OrgDisableTracking); + await flags['target-org'].setTracksSource(false); + this.logSuccess(messages.getMessage('success', [flags['target-org'].getUsername()])); + return { + tracksSource: await flags['target-org'].tracksSource(), + username: flags['target-org'].getUsername() as string, + }; + } +} diff --git a/src/commands/org/enable/tracking.ts b/src/commands/org/enable/tracking.ts new file mode 100644 index 00000000..cd4e26d8 --- /dev/null +++ b/src/commands/org/enable/tracking.ts @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023, 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 { dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { SfCommand, Flags } from '@salesforce/sf-plugins-core'; +import { Messages } from '@salesforce/core'; + +Messages.importMessagesDirectory(dirname(fileURLToPath(import.meta.url))); +const messages = Messages.loadMessages('@salesforce/plugin-org', 'org.enable.tracking'); + +export type OrgEnableTrackingResult = { + tracksSource: boolean; + username: string; +}; + +export default class OrgEnableTracking 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 readonly flags = { + 'target-org': Flags.requiredOrg(), + }; + + public async run(): Promise { + const { flags } = await this.parse(OrgEnableTracking); + + // can this org do tracking? + if (!(await flags['target-org'].supportsSourceTracking())) { + throw messages.createError('error.TrackingNotAvailable'); + } + + await flags['target-org'].setTracksSource(true); + this.logSuccess(messages.getMessage('success', [flags['target-org'].getUsername()])); + return { + tracksSource: await flags['target-org'].tracksSource(), + username: flags['target-org'].getUsername() as string, + }; + } +} diff --git a/test/nut/tracking.nut.ts b/test/nut/tracking.nut.ts new file mode 100644 index 00000000..ba2ab70d --- /dev/null +++ b/test/nut/tracking.nut.ts @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2023, 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 { dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { execCmd, TestSession } from '@salesforce/cli-plugins-testkit'; +import { expect } from 'chai'; +import { Messages } from '@salesforce/core'; +import { OrgEnableTrackingResult } from '../../src/commands/org/enable/tracking.js'; +import { OrgDisableTrackingResult } from '../../src/commands/org/disable/tracking.js'; + +Messages.importMessagesDirectory(dirname(fileURLToPath(import.meta.url))); +const messages = Messages.loadMessages('@salesforce/plugin-org', 'org.enable.tracking'); + +describe('org enable/disable tracking NUTs', () => { + let session: TestSession; + + before(async () => { + session = await TestSession.create({ + devhubAuthStrategy: 'AUTO', + project: { name: 'orgEnableDisableTrackingNut' }, + scratchOrgs: [{ setDefault: true, edition: 'developer' }], + }); + }); + + after(async () => { + await session?.clean(); + }); + + it('should disable on scratch org', () => { + const result = execCmd('org disable tracking --json', { ensureExitCode: 0 }).jsonOutput + ?.result; + expect(result?.tracksSource).to.equal(false); + expect(result?.username).to.equal(session.orgs.get('default')?.username); + }); + + it('should disable on scratch org (idempotency)', () => { + const result = execCmd('org disable tracking --json', { ensureExitCode: 0 }).jsonOutput + ?.result; + expect(result?.tracksSource).to.equal(false); + expect(result?.username).to.equal(session.orgs.get('default')?.username); + }); + + it('should re-enable on scratch org', () => { + const result = execCmd('org enable tracking --json', { ensureExitCode: 0 }).jsonOutput + ?.result; + expect(result?.tracksSource).to.equal(true); + expect(result?.username).to.equal(session.orgs.get('default')?.username); + }); + + it('should enable on scratch org (idempotency)', () => { + const result = execCmd('org enable tracking --json', { ensureExitCode: 0 }).jsonOutput + ?.result; + expect(result?.tracksSource).to.equal(true); + expect(result?.username).to.equal(session.orgs.get('default')?.username); + }); + + it('should disable on hub org (idempotency)', () => { + const result = execCmd(`org disable tracking -o ${session.hubOrg.username} --json`, { + ensureExitCode: 0, + }).jsonOutput?.result; + expect(result?.tracksSource).to.equal(false); + expect(result?.username).to.equal(session.hubOrg.username); + }); + + it('should fail to enable on hub org', () => { + const err = execCmd(`org enable tracking -o ${session.hubOrg.username}`, { + ensureExitCode: 1, + }); + expect(err?.shellOutput.stderr).to.include(messages.getMessage('error.TrackingNotAvailable')); + }); +});