Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

W-16441894 feat: @oclif/multi-stage-output #1120

Merged
merged 45 commits into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
a644056
feat: use ink for org create updates
mdonnalley Jun 21, 2024
93806d1
chore: linting and cleanup
mdonnalley Jun 24, 2024
7942e60
feat: fully functional now
mdonnalley Jun 27, 2024
3cd9746
chore: linting
mdonnalley Jun 27, 2024
b62f6ab
Merge branch 'main' into mdonnalley/ink
mdonnalley Jun 27, 2024
da220d5
chore(release): 4.3.1-dev.0 [skip ci]
svc-cli-bot Jun 27, 2024
8a8e9c9
fix: import messages dir
mdonnalley Jun 27, 2024
74ae4a9
chore(release): 4.3.1-dev.1 [skip ci]
svc-cli-bot Jun 27, 2024
f20f8f7
fix: async components
mdonnalley Jul 1, 2024
48e20f6
chore: try different methods
mdonnalley Jul 1, 2024
a627e04
chore: add comment
mdonnalley Jul 8, 2024
4f992b7
feat: dry up code and improve ux
mdonnalley Jul 11, 2024
5c04dc8
feat: dry up code and improve ux
mdonnalley Jul 11, 2024
956e787
chore: clean up
mdonnalley Jul 16, 2024
408e0dd
chore: clean up
mdonnalley Jul 16, 2024
2d0cdc5
feat: use ink for resume scratch
mdonnalley Jul 16, 2024
9fff633
fix: dont render when json
mdonnalley Jul 16, 2024
26fff88
chore: more clean up
mdonnalley Jul 16, 2024
b127e9e
chore: more clean up
mdonnalley Jul 16, 2024
dd3de51
chore: fine tune design
mdonnalley Jul 17, 2024
3fa4326
Merge branch 'main' into mdonnalley/ink
mdonnalley Jul 17, 2024
c33603f
chore(release): 4.3.6-dev.0 [skip ci]
svc-cli-bot Jul 17, 2024
76d39f3
chore: attempt to make multi stage framework
mdonnalley Jul 17, 2024
75b9ffd
chore: getting closer
mdonnalley Jul 18, 2024
74d86c5
chore(release): 4.3.6-dev.1 [skip ci]
svc-cli-bot Jul 18, 2024
f0af192
chore: various improvements
mdonnalley Jul 18, 2024
4a6f212
chore(release): 4.3.6-dev.2 [skip ci]
svc-cli-bot Jul 18, 2024
d2df143
feat: ci design
mdonnalley Jul 19, 2024
164fe45
feat: lots of improvements
mdonnalley Jul 19, 2024
86be7b5
chore: clean up
mdonnalley Jul 19, 2024
da62236
fix: put space in Text compnoent
mdonnalley Jul 19, 2024
880717d
fix: cached org might not exist
mdonnalley Jul 22, 2024
742dbb1
chore: small improvements
mdonnalley Jul 22, 2024
6b563c8
feat: add messages
mdonnalley Jul 22, 2024
4f6f2e1
feat: allow info to be print under stage
mdonnalley Jul 22, 2024
41be8df
feat: more flexibility
mdonnalley Jul 23, 2024
7af1dd9
chore: rename things
mdonnalley Jul 24, 2024
d8b35f5
feat: use oclif/multi-stage-output
mdonnalley Aug 7, 2024
724a638
chore: bump deps
mdonnalley Aug 16, 2024
770110f
Merge branch 'main' into mdonnalley/ink
mdonnalley Aug 16, 2024
f7fbab6
fix: remove extra new lines
mdonnalley Aug 16, 2024
5ad0309
fix: use sf-plugins-core
mdonnalley Aug 16, 2024
a52a595
chore: bump MultiStageOutput
mdonnalley Aug 19, 2024
0b0cb62
Merge branch 'main' into mdonnalley/ink
mdonnalley Aug 19, 2024
85d306a
chore: revert README
mdonnalley Aug 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@
"bugs": "https://github.com/forcedotcom/cli/issues",
"dependencies": {
"@oclif/core": "^4.0.16",
"@oclif/multi-stage-output": "^0.3.0",
"@salesforce/core": "^8.4.0",
"@salesforce/kit": "^3.2.0",
"@salesforce/sf-plugins-core": "^11.3.3",
"@salesforce/source-deploy-retrieve": "^12.1.12",
"ansis": "^3.2.0",
"change-case": "^5.4.4",
"is-wsl": "^3.1.0",
"open": "^10.1.0"
"open": "^10.1.0",
"terminal-link": "^3.0.0"
},
"devDependencies": {
"@oclif/plugin-command-snapshot": "^5.2.12",
Expand Down
66 changes: 50 additions & 16 deletions src/commands/org/create/scratch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,28 @@
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import { MultiStageOutput } from '@oclif/multi-stage-output';
import {
Lifecycle,
Messages,
Org,
scratchOrgCreate,
ScratchOrgLifecycleEvent,
scratchOrgLifecycleEventName,
scratchOrgLifecycleStages,
SfError,
} from '@salesforce/core';
import { Flags, SfCommand } from '@salesforce/sf-plugins-core';
import { Duration } from '@salesforce/kit';
import terminalLink from 'terminal-link';
import { buildScratchOrgRequest } from '../../../shared/scratchOrgRequest.js';
import { buildStatus } from '../../../shared/scratchOrgOutput.js';
import { ScratchCreateResponse } from '../../../shared/orgTypes.js';

Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('@salesforce/plugin-org', 'create_scratch');

const definitionFileHelpGroupName = 'Definition File Override';

export default class OrgCreateScratch extends SfCommand<ScratchCreateResponse> {
public static readonly summary = messages.getMessage('summary');
public static readonly description = messages.getMessage('description');
Expand Down Expand Up @@ -167,38 +170,69 @@ export default class OrgCreateScratch extends SfCommand<ScratchCreateResponse> {
flags,
flags['client-id'] ? await this.secretPrompt({ message: messages.getMessage('prompt.secret') }) : undefined
);
let lastStatus: string | undefined;

if (!flags.async) {
lifecycle.on<ScratchOrgLifecycleEvent>(scratchOrgLifecycleEventName, async (data): Promise<void> => {
lastStatus = buildStatus(data, baseUrl);
this.spinner.status = lastStatus;
return Promise.resolve();
});
}
this.log();
this.spinner.start(
flags.async ? 'Requesting Scratch Org (will not wait for completion because --async)' : 'Creating Scratch Org'
);
const stager = new MultiStageOutput<ScratchOrgLifecycleEvent & { alias: string | undefined }>({
stages: flags.async ? ['prepare request', 'send request', 'done'] : scratchOrgLifecycleStages,
title: flags.async ? 'Creating Scratch Org (async)' : 'Creating Scratch Org',
data: { alias: flags.alias },
jsonEnabled: this.jsonEnabled(),
postStagesBlock: [
{
label: 'Request Id',
type: 'dynamic-key-value',
get: (data) =>
data?.scratchOrgInfo?.Id && terminalLink(data.scratchOrgInfo.Id, `${baseUrl}/${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<ScratchOrgLifecycleEvent>(scratchOrgLifecycleEventName, async (data): Promise<void> => {
stager.goto(data.stage, data);
if (data.stage === 'done') {
stager.stop();
}
return Promise.resolve();
});

try {
const { username, scratchOrgInfo, authFields, warnings } = await scratchOrgCreate(createCommandOptions);

this.spinner.stop(lastStatus);
if (!scratchOrgInfo) {
throw new SfError('The scratch org did not return with any information');
}
this.log();

if (flags.async) {
stager.goto('done', { scratchOrgInfo });
stager.stop();
this.info(messages.getMessage('action.resume', [this.config.bin, scratchOrgInfo.Id]));
} else {
this.logSuccess(messages.getMessage('success'));
}

return { username, scratchOrgInfo, authFields, warnings, orgId: authFields?.orgId };
} catch (error) {
stager.stop(error as Error);
if (error instanceof SfError && error.name === 'ScratchOrgInfoTimeoutError') {
this.spinner.stop(lastStatus);
const scratchOrgInfoId = (error.data as { scratchOrgInfoId: string }).scratchOrgInfoId;
const resumeMessage = messages.getMessage('action.resume', [this.config.bin, scratchOrgInfoId]);

Expand Down
2 changes: 1 addition & 1 deletion src/commands/org/delete/sandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const messages = Messages.loadMessages('@salesforce/plugin-org', 'delete_sandbox
export type SandboxDeleteResponse = {
orgId: string;
username: string;
}
};

export default class DeleteSandbox extends SfCommand<SandboxDeleteResponse> {
public static readonly summary = messages.getMessage('summary');
Expand Down
2 changes: 1 addition & 1 deletion src/commands/org/delete/scratch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const messages = Messages.loadMessages('@salesforce/plugin-org', 'delete_scratch
export type ScratchDeleteResponse = {
orgId: string;
username: string;
}
};

export default class DeleteScratch extends SfCommand<ScratchDeleteResponse> {
public static readonly summary = messages.getMessage('summary');
Expand Down
56 changes: 46 additions & 10 deletions src/commands/org/resume/scratch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ import {
ScratchOrgCache,
ScratchOrgLifecycleEvent,
scratchOrgLifecycleEventName,
scratchOrgLifecycleStages,
scratchOrgResume,
SfError,
} from '@salesforce/core';
import terminalLink from 'terminal-link';
import { MultiStageOutput } from '@oclif/multi-stage-output';
import { ScratchCreateResponse } from '../../../shared/orgTypes.js';
import { buildStatus } from '../../../shared/scratchOrgOutput.js';

Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('@salesforce/plugin-org', 'resume_scratch');
Expand Down Expand Up @@ -55,26 +57,60 @@ export default class OrgResumeScratch extends SfCommand<ScratchCreateResponse> {

// 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 hubBaseUrl = cache.get(jobId)?.hubBaseUrl;
let lastStatus: string | undefined;
const cached = cache.get(jobId);
const hubBaseUrl = cached?.hubBaseUrl;

const stager = new MultiStageOutput<ScratchOrgLifecycleEvent & { alias: string | undefined }>({
stages: scratchOrgLifecycleStages,
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<ScratchOrgLifecycleEvent>(scratchOrgLifecycleEventName, async (data): Promise<void> => {
lastStatus = buildStatus(data, hubBaseUrl);
this.spinner.status = lastStatus;
stager.goto(data.stage, data);
if (data.stage === 'done') {
stager.stop();
}
return Promise.resolve();
});

this.log();
this.spinner.start('Creating Scratch Org');

try {
const { username, scratchOrgInfo, authFields, warnings } = await scratchOrgResume(jobId);
this.spinner.stop(lastStatus);

this.log();
this.logSuccess(messages.getMessage('success'));
return { username, scratchOrgInfo, authFields, warnings, orgId: authFields?.orgId };
} catch (e) {
stager.stop(e as 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]);
Expand Down
2 changes: 1 addition & 1 deletion src/shared/orgHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ type PostOrgCreateOpts = HookOpts<OrgCreateResult>;
*/
export type OrgHooks = {
postorgcreate: PostOrgCreateOpts;
} & Interfaces.Hooks
} & Interfaces.Hooks;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type OrgHook<T> = (this: Hook.Context, options: T extends keyof Interfaces.Hooks ? OrgHooks[T] : T) => any;
Expand Down
41 changes: 0 additions & 41 deletions src/shared/scratchOrgOutput.ts

This file was deleted.

71 changes: 0 additions & 71 deletions test/shared/scratchOrgOutput.test.ts

This file was deleted.

6 changes: 3 additions & 3 deletions test/shared/scratchOrgRequest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ import { Config, Interfaces } from '@oclif/core';
import { expect } from 'chai';
import { MockTestOrgData, TestContext } from '@salesforce/core/testSetup';
import { buildScratchOrgRequest } from '../../src/shared/scratchOrgRequest.js';
import EnvCreateScratch from '../../src/commands/org/create/scratch.js';
import OrgCreateScratch from '../../src/commands/org/create/scratch.js';

class Wrapper extends EnvCreateScratch {
class Wrapper extends OrgCreateScratch {
// simple method to return the parsed flags so they can be used in the tests
public async getFlags(): Promise<Interfaces.InferredFlags<typeof Wrapper.flags>> {
return (await this.parse(Wrapper)).flags;
}
}

/** pass in the params in array form, get back the parsed flags */
const paramsToFlags = async (params: string[]): Promise<Interfaces.InferredFlags<typeof EnvCreateScratch.flags>> =>
const paramsToFlags = async (params: string[]): Promise<Interfaces.InferredFlags<typeof OrgCreateScratch.flags>> =>
new Wrapper(params, { runHook: () => ({ successes: [], failures: [] }) } as unknown as Config).getFlags();

describe('buildScratchOrgRequest function', () => {
Expand Down
4 changes: 2 additions & 2 deletions test/unit/org/resumeScratch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe('org:resume:scratch', () => {
});

try {
await OrgResumeScratch.run(['--job-id', '2SRFOOFOOFOOFOOFOO']);
await OrgResumeScratch.run(['--job-id', '2SRFOOFOOFOOFOOFOO', '--json']);
expect(false, 'ResumeSandbox should have thrown sandboxCreateNotComplete');
} catch (err: unknown) {
const error = err as SfError;
Expand All @@ -50,7 +50,7 @@ describe('org:resume:scratch', () => {
});

try {
await OrgResumeScratch.run(['--job-id', '2SRFOOFOOFOOFOOFOO']);
await OrgResumeScratch.run(['--job-id', '2SRFOOFOOFOOFOOFOO', '--json']);
expect(false, 'ResumeSandbox should have thrown sandboxCreateNotComplete');
} catch (err: unknown) {
const error = err as SfError;
Expand Down
Loading
Loading