Skip to content

Conversation

@alexanderMontague
Copy link
Contributor

@alexanderMontague alexanderMontague commented Nov 18, 2025

WHY are these changes introduced?

Part of: https://github.com/shop/issues-develop/issues/21594

  • We're trying to improve our local development experience for using the CLI
  • A good first step is being able to hit our APIs without the identity service running

WHAT is this pull request doing?

  • scaffolds out the interface for the identity client that we will use
  • defines the client swapping capabilities based on the environment and what services are running

How to test your changes?

  • included tests
  • looking for feedback on a better singleton pattern if possible


let _identityClient: IdentityBaseClient | undefined

export function getIdentityClient() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really wanted this to live in IdentityBaseClient under a getInstance method like we do for our other clients, but combining that with an abstract class leads to circular dependencies. I see we use this singleton pattern in other modules, so looking for feedback on this especially.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think is OK to have this here 👌

@github-actions
Copy link
Contributor

github-actions bot commented Nov 18, 2025

Coverage report

St.
Category Percentage Covered / Total
🟡 Statements
79.17% (-0.06% 🔻)
13615/17198
🟡 Branches
73.12% (+0.01% 🔼)
6640/9081
🟡 Functions
79.18% (-0.19% 🔻)
3510/4433
🟡 Lines
79.53% (-0.06% 🔻)
12860/16171
Show new covered files 🐣
St.
File Statements Branches Functions Lines
🟢
... / bulk-operation-run-query.ts
100% 100% 100% 100%
🟢
... / execute-bulk-operation.ts
100% 83.33% 100% 100%
🟢
... / run-query.ts
100% 100% 100% 100%
🔴
... / identity-client.ts
0% 100% 0% 0%
🔴
... / identity-mock-client.ts
0% 100% 0% 0%
🔴
... / identity-service-client.ts
0% 100% 0% 0%
🟢
... / instance.ts
100% 100% 100% 100%
Show files with reduced coverage 🔻
St.
File Statements Branches Functions Lines
🟢
... / ConcurrentOutput.tsx
98.36% (-1.64% 🔻)
92% (-4% 🔻)
100%
98.33% (-1.67% 🔻)
🔴
... / ui.tsx
50.82% (-0.79% 🔻)
42.86% (-5.53% 🔻)
54.55% (+1.42% 🔼)
50% (-0.82% 🔻)
🟡
... / theme-environment.ts
69.57% (-1.86% 🔻)
50%
55.56% (-3.27% 🔻)
69.57% (-1.86% 🔻)

Test suite run success

3364 tests passing in 1378 suites.

Report generated by 🧪jest coverage report action from ec62db8

Copy link
Contributor

@MitchDickinson MitchDickinson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some quick drive by interface feedback 😅

if (!_identityClient) {
const isLocal = serviceEnvironment() === Environment.Local
const identityServiceRunning = isRunning2024('identity')
const client = isLocal && !identityServiceRunning ? new IdentityMockClient() : new IdentityServiceClient()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not just return client and get rid of the mutable variable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just lends itself to the singleton pattern better. Saves re-instantiating on every call, but the clients are deterministic so we could do this too. Follows existing patterns, I don't feel too strongly about this. LMK what you think

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My opinion is that the tradeoff isn't worth it -- it introduces sequencing where the variable can be undefined, which has to be accounted for in the type as well as in all processes it's called from. The perf gains on compute will be near-zero, and may never actually become concrete unless you're calling identity multiple times in the same command lifecycle but different execution context.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would you ever access the undefined variable?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mutable variable is private, only the getIdentityClient is public and will return a valid client always, regardless of using a singleton or instantiating a new client every time.

I think is ok, to have a singleton here. Is safe and I think there is any con about it.

If eventually we decide that this client will have logic to prevent multiple refresh tokens operations from running at the same time, then we need to ensure everyone is using the same instance. (is an example, this is controlled somewhere else now)

@alexanderMontague alexanderMontague force-pushed the 11-18-create_indentity_client_interface branch from bd49456 to ec62db8 Compare November 20, 2025 16:14
@github-actions
Copy link
Contributor

Differences in type declarations

We detected differences in the type declarations generated by Typescript for this branch compared to the baseline ('main' branch). Please, review them to ensure they are backward-compatible. Here are some important things to keep in mind:

  • Some seemingly private modules might be re-exported through public modules.
  • If the branch is behind main you might see odd diffs, rebase main into this branch.

New type declarations

packages/cli-kit/dist/private/node/clients/identity/identity-client.d.ts
import { ApplicationToken, IdentityToken } from '../../session/schema.js';
import { ExchangeScopes } from '../../session/exchange.js';
import { API } from '../../api.js';
export declare abstract class IdentityClient {
    abstract requestAccessToken(scopes: string[]): Promise<IdentityToken>;
    abstract exchangeAccessForApplicationTokens(identityToken: IdentityToken, scopes: ExchangeScopes, store?: string): Promise<{
        [x: string]: ApplicationToken;
    }>;
    abstract refreshAccessToken(currentToken: IdentityToken): Promise<IdentityToken>;
    clientId(): string;
    applicationId(_api: API): string;
}
packages/cli-kit/dist/private/node/clients/identity/identity-mock-client.d.ts
import { IdentityClient } from './identity-client.js';
import { ApplicationToken, IdentityToken } from '../../session/schema.js';
import { ExchangeScopes } from '../../session/exchange.js';
export declare class IdentityMockClient extends IdentityClient {
    requestAccessToken(_scopes: string[]): Promise<IdentityToken>;
    exchangeAccessForApplicationTokens(_identityToken: IdentityToken, _scopes: ExchangeScopes, _store?: string): Promise<{
        [x: string]: ApplicationToken;
    }>;
    refreshAccessToken(_currentToken: IdentityToken): Promise<IdentityToken>;
}
packages/cli-kit/dist/private/node/clients/identity/identity-service-client.d.ts
import { IdentityClient } from './identity-client.js';
import { ApplicationToken, IdentityToken } from '../../session/schema.js';
import { ExchangeScopes } from '../../session/exchange.js';
export declare class IdentityServiceClient extends IdentityClient {
    requestAccessToken(_scopes: string[]): Promise<IdentityToken>;
    exchangeAccessForApplicationTokens(_identityToken: IdentityToken, _scopes: ExchangeScopes, _store?: string): Promise<{
        [x: string]: ApplicationToken;
    }>;
    refreshAccessToken(_currentToken: IdentityToken): Promise<IdentityToken>;
}
packages/cli-kit/dist/private/node/clients/identity/instance.d.ts
import { IdentityClient } from './identity-client.js';
export declare function getIdentityClient(): IdentityClient;

Existing type declarations

packages/cli-kit/dist/public/node/vendor/dev_server/dev-server-2024.d.ts
@@ -4,6 +4,7 @@ export declare function createServer(projectName: string): {
     url: (options?: HostOptions) => string;
 };
 declare function assertRunning2024(projectName: string): void;
+export declare function isRunning2024(projectName: string): boolean;
 declare let assertRunningOverride: typeof assertRunning2024 | undefined;
 export declare function setAssertRunning(override: typeof assertRunningOverride): void;
 export {};
\ No newline at end of file

@alexanderMontague alexanderMontague marked this pull request as ready for review November 20, 2025 22:06
@alexanderMontague alexanderMontague requested a review from a team as a code owner November 20, 2025 22:06
@github-actions
Copy link
Contributor

We detected some changes at packages/*/src and there are no updates in the .changeset.
If the changes are user-facing, run pnpm changeset add to track your changes and include them in the next release CHANGELOG.

Caution

DO NOT create changesets for features which you do not wish to be included in the public changelog of the next CLI release.

@alexanderMontague alexanderMontague changed the title create indentity client interface create identity client interface Nov 20, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants