@salesforce/plugin-org
Version:
Commands to interact with Salesforce orgs
155 lines • 6.86 kB
JavaScript
/*
* Copyright 2026, Salesforce, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { strict as assert } from 'node:assert';
import { Flags, SfCommand } from '@salesforce/sf-plugins-core';
import { envVars, Lifecycle, Messages, ScratchOrgCache, scratchOrgLifecycleEventName, scratchOrgLifecycleStages, scratchOrgResume, SfError, } from '@salesforce/core';
import terminalLink from 'terminal-link';
import { MultiStageOutput } from '@oclif/multi-stage-output';
import { capitalCase } from 'change-case';
import { omit } from '@salesforce/kit';
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('@salesforce/plugin-org', 'resume_scratch');
const secretsMessages = Messages.loadMessages('@salesforce/plugin-org', 'secrets-redacted');
export default class OrgResumeScratch extends SfCommand {
static summary = messages.getMessage('summary');
static description = messages.getMessage('description');
static examples = messages.getMessages('examples');
static aliases = ['env:resume:scratch'];
static deprecateAliases = true;
static flags = {
'job-id': Flags.salesforceId({
char: 'i',
length: 'both',
summary: messages.getMessage('flags.job-id.summary'),
description: messages.getMessage('flags.job-id.description'),
exactlyOne: ['use-most-recent', 'job-id'],
startsWith: '2SR',
}),
'use-most-recent': Flags.boolean({
char: 'r',
summary: messages.getMessage('flags.use-most-recent.summary'),
exactlyOne: ['use-most-recent', 'job-id'],
}),
wait: Flags.duration({
char: 'w',
summary: messages.getMessage('flags.wait.summary'),
description: messages.getMessage('flags.wait.description'),
min: 0,
unit: 'minutes',
helpValue: '<minutes>',
defaultValue: 0,
}),
};
async run() {
const { flags } = await this.parse(OrgResumeScratch);
const cache = await ScratchOrgCache.create();
const lifecycle = Lifecycle.getInstance();
const jobId = flags['use-most-recent'] ? cache.getLatestKey() : flags['job-id'];
if (!jobId && flags['use-most-recent'])
throw messages.createError('error.NoRecentJobId');
// oclif doesn't know that the exactlyOne flag will ensure that one of these is set, and there we definitely have a jobID.
assert(jobId);
const cached = cache.get(jobId);
const hubBaseUrl = cached?.hubBaseUrl;
const mso = new MultiStageOutput({
stages: scratchOrgLifecycleStages.map((stage) => capitalCase(stage)),
title: 'Resuming Scratch Org',
data: { alias: cached?.alias },
jsonEnabled: this.jsonEnabled(),
postStagesBlock: [
{
label: 'Request Id',
type: 'dynamic-key-value',
get: (data) => data?.scratchOrgInfo?.Id && terminalLink(data.scratchOrgInfo.Id, `${hubBaseUrl}/${data.scratchOrgInfo.Id}`),
bold: true,
},
{
label: 'OrgId',
type: 'dynamic-key-value',
get: (data) => data?.scratchOrgInfo?.ScratchOrg,
bold: true,
color: 'cyan',
},
{
label: 'Username',
type: 'dynamic-key-value',
get: (data) => data?.scratchOrgInfo?.SignupUsername,
bold: true,
color: 'cyan',
},
{
label: 'Alias',
type: 'static-key-value',
get: (data) => data?.alias,
},
],
});
lifecycle.on(scratchOrgLifecycleEventName, async (data) => {
mso.skipTo(capitalCase(data.stage), data);
if (data.stage === 'done') {
mso.stop();
}
return Promise.resolve();
});
try {
const { username, scratchOrgInfo, authFields, warnings } = await scratchOrgResume(jobId, flags.wait);
this.log();
this.logSuccess(messages.getMessage('success'));
// TODO: Remove env var workaround
const showSecretsEnvVarIsSet = envVars.getBoolean('SF_TEMP_SHOW_SECRETS', false);
const accessTokenRedacted = secretsMessages.getMessage('redacted.accessToken');
const redactedAuthFields = authFields
? {
...authFields,
accessToken: showSecretsEnvVarIsSet ? authFields.accessToken : accessTokenRedacted,
refreshToken: undefined,
clientSecret: undefined,
}
: undefined;
const redactedScratchOrgInfo = scratchOrgInfo ? omit(scratchOrgInfo, ['AuthCode']) : undefined;
if (this.jsonEnabled()) {
if (showSecretsEnvVarIsSet) {
this.warn(secretsMessages.getMessage('temp.envVarIsSet', ['sf org resume scratch --json']));
}
else {
this.warn(secretsMessages.getMessage('temp.envVarWorkaround', ['sf org resume scratch --json']));
}
}
return {
username,
scratchOrgInfo: redactedScratchOrgInfo,
authFields: redactedAuthFields,
warnings,
orgId: authFields?.orgId,
};
}
catch (e) {
mso.error();
if (cache.keys() && e instanceof Error && e.name === 'CacheMissError') {
// we have something in the cache, but it didn't match what the user passed in
throw messages.createError('error.jobIdMismatch', [jobId]);
}
else if (e instanceof SfError && e.name === 'StillInProgressError') {
e.actions = messages.getMessages('StillInProgressError.actions');
throw e;
}
else {
throw SfError.wrap(e);
}
}
}
}
//# sourceMappingURL=scratch.js.map