Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 1 addition & 2 deletions packages/app/src/cli/services/dev/fetch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,8 @@ afterEach(() => {
})

describe('fetchOrganizations', async () => {
test('returns fetched organizations from Partners and App Management for 1P development', async () => {
test('returns fetched organizations from available developer platform clients', async () => {
// Given
vi.stubEnv('SHOPIFY_CLI_1P_DEV', 'true')
vi.mocked(blockPartnersAccess).mockReturnValue(false)
const partnersClient: PartnersClient = testDeveloperPlatformClient({
organizations: () => Promise.resolve([ORG1]),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {DevSessionStatusManager} from './dev-session-status-manager.js'
import {DeveloperPlatformClient} from '../../../../utilities/developer-platform-client.js'
import {AppLinkedInterface} from '../../../../models/app/app.js'
import {AppEventWatcher} from '../../app-events/app-event-watcher.js'
import {buildAppURLForWeb} from '../../../../utilities/app/app-url.js'
import {
testAppAccessConfigExtension,
testAppLinked,
Expand All @@ -20,7 +19,6 @@ import {AbortSignal, AbortController} from '@shopify/cli-kit/node/abort'
import {flushPromises} from '@shopify/cli-kit/node/promises'
import * as outputContext from '@shopify/cli-kit/node/ui/components'
import {readdir} from '@shopify/cli-kit/node/fs'
import {firstPartyDev} from '@shopify/cli-kit/node/context/local'
import {SerialBatchProcessor} from '@shopify/cli-kit/node/serial-batch-processor'

vi.mock('@shopify/cli-kit/node/fs')
Expand All @@ -29,14 +27,6 @@ vi.mock('@shopify/cli-kit/node/http')
vi.mock('../../../../utilities/app/app-url.js')
vi.mock('node-fetch')
vi.mock('../../../bundle.js')
vi.mock('@shopify/cli-kit/node/context/local', async (importOriginal) => {
const original = await importOriginal<typeof import('@shopify/cli-kit/node/context/local')>()
return {
...original,
firstPartyDev: vi.fn().mockReturnValue(false),
}
})

describe('setupDevSessionProcess', () => {
test('returns a dev session process with correct configuration', async () => {
// Given
Expand All @@ -50,7 +40,6 @@ describe('setupDevSessionProcess', () => {
appId: 'app123',
appWatcher: {} as AppEventWatcher,
appPreviewURL: 'https://test.preview.url',
appLocalProxyURL: 'https://test.local.url',
devSessionStatusManager: new DevSessionStatusManager(),
}

Expand All @@ -72,7 +61,6 @@ describe('setupDevSessionProcess', () => {
appId: options.appId,
appWatcher: options.appWatcher,
appPreviewURL: options.appPreviewURL,
appLocalProxyURL: options.appLocalProxyURL,
devSessionStatusManager: options.devSessionStatusManager,
},
})
Expand Down Expand Up @@ -108,7 +96,6 @@ describe('pushUpdatesForDevSession', () => {
appId: 'app123',
organizationId: 'org123',
appPreviewURL: 'https://test.preview.url',
appLocalProxyURL: 'https://test.local.url',
devSessionStatusManager,
}
})
Expand Down Expand Up @@ -197,7 +184,6 @@ describe('pushUpdatesForDevSession', () => {

test('handles scope changes and displays updated message', async () => {
// Given
vi.mocked(buildAppURLForWeb).mockResolvedValue('https://test.myshopify.com/admin/apps/test')
const appAccess = await testAppAccessConfigExtension(false, undefined, false)
const event = {extensionEvents: [{type: 'updated', extension: appAccess}], app}
const contextSpy = vi.spyOn(outputContext, 'useConcurrentOutputContext')
Expand All @@ -219,9 +205,8 @@ describe('pushUpdatesForDevSession', () => {
contextSpy.mockRestore()
})

test('updates preview URL to appPreviewURL by default (local dev console only for 1P devs)', async () => {
// Given - dev console is NOT shown by default (only for 1P devs)
vi.mocked(firstPartyDev).mockReturnValue(false)
test('updates preview URL to appPreviewURL when previewable extensions change', async () => {
// Given
const extension = await testUIExtension({type: 'ui_extension'})
const newApp = testAppLinked({allExtensions: [extension]})

Expand All @@ -236,24 +221,6 @@ describe('pushUpdatesForDevSession', () => {
expect(devSessionStatusManager.status.previewURL).toBe(options.appPreviewURL)
})

test('updates preview URL to appLocalProxyURL when 1P dev has previewable extensions', async () => {
// Given - dev console is shown for 1P devs with previewable extensions
vi.mocked(firstPartyDev).mockReturnValue(true)
const extension = await testUIExtension({type: 'ui_extension'})
const newApp = testAppLinked({allExtensions: [extension]})

// When
await pushUpdatesForDevSession({stderr, stdout, abortSignal: abortController.signal}, options)
await appWatcher.start({stdout, stderr, signal: abortController.signal})
await flushPromises()
appWatcher.emit('all', {app: newApp, extensionEvents: [{type: 'updated', extension}]})
await flushPromises()

// Then
expect(devSessionStatusManager.status.previewURL).toBe(options.appLocalProxyURL)
vi.mocked(firstPartyDev).mockReturnValue(false)
})

test('updates preview URL to appPreviewURL when no previewable extensions', async () => {
// Given
const extension = await testFlowActionExtension()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ export interface DevSessionProcessOptions {
appId: string
appWatcher: AppEventWatcher
appPreviewURL: string
appLocalProxyURL: string
devSessionStatusManager: DevSessionStatusManager
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {endHRTimeInMs, startHRTime} from '@shopify/cli-kit/node/hrtime'
import {ClientError} from 'graphql-request'
import {JsonMapType} from '@shopify/cli-kit/node/toml'
import {AbortError} from '@shopify/cli-kit/node/error'
import {firstPartyDev, isUnitTest} from '@shopify/cli-kit/node/context/local'
import {isUnitTest} from '@shopify/cli-kit/node/context/local'
import {dirname, joinPath} from '@shopify/cli-kit/node/path'
import {readdir} from '@shopify/cli-kit/node/fs'
import {SerialBatchProcessor} from '@shopify/cli-kit/node/serial-batch-processor'
Expand Down Expand Up @@ -260,18 +260,13 @@ export class DevSession {
}

/**
* Update the preview URL, it only changes if we move between a non-previewable state and a previewable state.
* (i.e. if we go from a state with no extensions to a state with ui-extensions or vice versa)
* Use local dev console only for 1P developers (SHOPIFY_CLI_1P_DEV is enabled).
* Update the preview URL when extension availability changes.
* @param event - The app event
*/
private updatePreviewURL(event: AppEvent) {
const hasPreview = event.app.allExtensions.filter((ext) => ext.isPreviewable).length > 0
const useDevConsole = firstPartyDev() && hasPreview
const newPreviewURL = useDevConsole ? this.options.appLocalProxyURL : this.options.appPreviewURL
const hasExtensions = event.app.nonConfigExtensions.length > 0
this.statusManager.updateStatus({
previewURL: newPreviewURL,
previewURL: this.options.appPreviewURL,
appEmbedded: event.app.configuration.embedded,
hasExtensions,
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import {Config} from '@oclif/core'
import {getEnvironmentVariables} from '@shopify/cli-kit/node/environment'
import {isStorefrontPasswordProtected} from '@shopify/theme'
import {fetchTheme} from '@shopify/cli-kit/node/themes/api'
import {firstPartyDev} from '@shopify/cli-kit/node/context/local'
import {adminFqdn} from '@shopify/cli-kit/node/context/fqdn'

vi.mock('../../context/identifiers.js')
Expand All @@ -46,7 +45,6 @@ vi.mock('../fetch.js')
vi.mock('@shopify/cli-kit/node/environment')
vi.mock('@shopify/theme')
vi.mock('@shopify/cli-kit/node/themes/api')
vi.mock('@shopify/cli-kit/node/context/local')
vi.mock('@shopify/cli-kit/node/context/fqdn', async (importOriginal) => {
const original = await importOriginal<typeof import('@shopify/cli-kit/node/context/fqdn')>()
return {
Expand Down Expand Up @@ -79,8 +77,6 @@ beforeEach(() => {
role: 'theme',
processing: false,
})
// By default, firstPartyDev is false (local dev console only shown for 1P devs)
vi.mocked(firstPartyDev).mockReturnValue(false)
vi.mocked(adminFqdn).mockResolvedValue('admin.shopify.com')
})

Expand Down Expand Up @@ -178,7 +174,6 @@ describe('setup-dev-processes', () => {
graphiqlKey,
})

// Dev console is NOT shown by default (only shown for 1P devs)
expect(res.previewUrl).toBe('https://admin.shopify.com/store/store/apps/api-key?dev-console=show')
expect(res.processes[0]).toMatchObject({
type: 'web',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,14 @@ import {AppLinkedInterface, getAppScopes, WebType} from '../../../models/app/app
import {OrganizationApp} from '../../../models/organization.js'
import {DevOptions} from '../../dev.js'
import {LocalhostCert, getProxyingWebServer} from '../../../utilities/app/http-reverse-proxy.js'
import {buildAppURLForAdmin, buildAppURLForWeb} from '../../../utilities/app/app-url.js'
import {buildAppURLForAdmin} from '../../../utilities/app/app-url.js'
import {ApplicationURLs} from '../urls.js'
import {DeveloperPlatformClient} from '../../../utilities/developer-platform-client.js'
import {AppEventWatcher} from '../app-events/app-event-watcher.js'
import {reloadApp} from '../../../models/app/loader.js'
import {resolveGraphiQLKey} from '@shopify/cli-kit/node/graphiql/server'
import {getAvailableTCPPort} from '@shopify/cli-kit/node/tcp'
import {isTruthy} from '@shopify/cli-kit/node/context/utilities'
import {firstPartyDev} from '@shopify/cli-kit/node/context/local'
import {getEnvironmentVariables} from '@shopify/cli-kit/node/environment'
import {outputInfo} from '@shopify/cli-kit/node/output'
import {adminFqdn} from '@shopify/cli-kit/node/context/fqdn'
Expand Down Expand Up @@ -99,26 +98,9 @@ export async function setupDevProcesses({
const reloadedApp = await reloadApp(localApp)
const appWatcher = new AppEventWatcher(reloadedApp, network.proxyUrl)

// Decide on the appropriate preview URL for a session with these processes
// - 1P developers with previewable extensions: use local dev console
// - 1P developers without previewable extensions: use legacy OAuth redirect URL
// - 3P developers: use unified admin URL
const anyPreviewableExtensions = reloadedApp.allExtensions.some((ext) => ext.isPreviewable)
const devConsoleURL = `${network.proxyUrl}/extensions/dev-console`
const is1PDev = firstPartyDev()

// appPreviewUrl is the direct app URL (used by GraphiQL and dev session fallback)
// previewURL is what's shown to the user (may be dev console for 1P devs)
let appPreviewUrl: string
if (is1PDev) {
appPreviewUrl = buildAppURLForWeb(storeFqdn, apiKey)
} else {
const adminDomain = await adminFqdn()
appPreviewUrl = buildAppURLForAdmin(storeFqdn, apiKey, adminDomain)
}

const useDevConsole = is1PDev && anyPreviewableExtensions
const previewURL = useDevConsole ? devConsoleURL : appPreviewUrl
const adminDomain = await adminFqdn()
const appPreviewUrl = buildAppURLForAdmin(storeFqdn, apiKey, adminDomain)
const previewURL = appPreviewUrl

const resolvedGraphiqlKey = resolveGraphiQLKey(graphiqlKey, apiSecret, storeFqdn)
const graphiqlURL = shouldRenderGraphiQL
Expand Down Expand Up @@ -182,7 +164,6 @@ export async function setupDevProcesses({
storeFqdn,
appWatcher,
appPreviewURL: appPreviewUrl,
appLocalProxyURL: devConsoleURL,
devSessionStatusManager,
})
: await setupDraftableExtensionsProcess({
Expand Down
1 change: 0 additions & 1 deletion packages/app/src/cli/services/dev/select-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {describe, expect, vi, test} from 'vitest'

vi.mock('../../prompts/dev')
vi.mock('./fetch')
vi.mock('@shopify/cli-kit/node/context/local')
vi.mock('@shopify/cli-kit/node/system')
vi.mock('@shopify/cli-kit/node/ui')

Expand Down
3 changes: 1 addition & 2 deletions packages/app/src/cli/services/dev/select-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
import {ClientName, DeveloperPlatformClient, Paginateable} from '../../utilities/developer-platform-client.js'
import {sleep} from '@shopify/cli-kit/node/system'
import {renderInfo, renderTasks} from '@shopify/cli-kit/node/ui'
import {firstPartyDev} from '@shopify/cli-kit/node/context/local'
import {AbortError, BugError, CancelExecution} from '@shopify/cli-kit/node/error'
import {outputSuccess} from '@shopify/cli-kit/node/output'

Expand Down Expand Up @@ -130,7 +129,7 @@ export async function convertToTransferDisabledStoreIfNeeded(
developerPlatformClient: DeveloperPlatformClient,
conversionMode: 'prompt-first' | 'never',
): Promise<boolean> {
if (store.transferDisabled || firstPartyDev()) return true
if (store.transferDisabled) return true

if (!store.transferDisabled && !store.convertableToPartnerTest) {
throw new AbortError(
Expand Down
3 changes: 0 additions & 3 deletions packages/app/src/cli/utilities/developer-platform-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ import {TokenItem} from '@shopify/cli-kit/node/ui'
import {blockPartnersAccess} from '@shopify/cli-kit/node/environment'
import {UnauthorizedHandler} from '@shopify/cli-kit/node/api/graphql'
import {JsonMapType} from '@shopify/cli-kit/node/toml'
import {firstPartyDev} from '@shopify/cli-kit/node/context/local'

export type {Store} from '../api/graphql/business-platform-organizations/generated/types.js'

Expand Down Expand Up @@ -109,8 +108,6 @@ function selectDeveloperPlatformClientByOrg(organization: Organization): Develop
}

function defaultDeveloperPlatformClient(): DeveloperPlatformClient {
if (firstPartyDev() && !blockPartnersAccess()) return PartnersClient.getInstance()

return AppManagementClient.getInstance()
}

Expand Down
26 changes: 2 additions & 24 deletions packages/cli-kit/src/private/node/api/headers.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {buildHeaders, sanitizedHeadersOutput, GraphQLClientError} from './headers.js'
import {CLI_KIT_VERSION} from '../../../public/common/version.js'
import {randomUUID} from '../../../public/node/crypto.js'
import {firstPartyDev, isUnitTest} from '../../../public/node/context/local.js'
import {isUnitTest} from '../../../public/node/context/local.js'

import {test, vi, expect, describe, beforeEach} from 'vitest'

Expand All @@ -14,30 +14,9 @@ beforeEach(() => {
})

describe('common API methods', () => {
test('headers are built correctly when firstPartyDev yields true', () => {
test('headers are built correctly', () => {
// Given
vi.mocked(randomUUID).mockReturnValue('random-uuid')
vi.mocked(firstPartyDev).mockReturnValue(true)
// When
const headers = buildHeaders('my-token')

// Then
const version = CLI_KIT_VERSION
expect(headers).toEqual({
'Content-Type': 'application/json',
'Keep-Alive': 'timeout=30',
'X-Shopify-Access-Token': 'Bearer my-token',
'User-Agent': `Shopify CLI; v=${version}`,
authorization: 'Bearer my-token',
'Sec-CH-UA-PLATFORM': process.platform,
'X-Shopify-Cli-Employee': '1',
})
})

test('when user is not employee, do not include header', () => {
// Given
vi.mocked(randomUUID).mockReturnValue('random-uuid')
vi.mocked(firstPartyDev).mockReturnValue(false)
// When
const headers = buildHeaders('my-token')

Expand All @@ -58,7 +37,6 @@ describe('common API methods', () => {
(prefix) => {
// Given
vi.mocked(randomUUID).mockReturnValue('random-uuid')
vi.mocked(firstPartyDev).mockReturnValue(false)
const token = `${prefix}_my_token`
// When
const headers = buildHeaders(token)
Expand Down
3 changes: 1 addition & 2 deletions packages/cli-kit/src/private/node/api/headers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {CLI_KIT_VERSION} from '../../../public/common/version.js'
import {firstPartyDev, isUnitTest, isVerbose} from '../../../public/node/context/local.js'
import {isUnitTest, isVerbose} from '../../../public/node/context/local.js'
import {AbortError} from '../../../public/node/error.js'
import https from 'https'

Expand Down Expand Up @@ -60,7 +60,6 @@ export function buildHeaders(token?: string): Record<string, string> {
// 'Sec-CH-UA': secCHUA, This header requires the Git sha.
'Sec-CH-UA-PLATFORM': process.platform,
'Content-Type': 'application/json',
...(firstPartyDev() && {'X-Shopify-Cli-Employee': '1'}),
}
if (token) {
const authString = token.match(/^shp(at|ua|ca|tka)/) ? token : `Bearer ${token}`
Expand Down
1 change: 0 additions & 1 deletion packages/cli-kit/src/private/node/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ export const environmentVariables = {
doctor: 'SHOPIFY_CLI_DOCTOR',
enableCliRedirect: 'SHOPIFY_CLI_ENABLE_CLI_REDIRECT',
env: 'SHOPIFY_CLI_ENV',
firstPartyDev: 'SHOPIFY_CLI_1P_DEV',
noAnalytics: 'SHOPIFY_CLI_NO_ANALYTICS',
optOutInstrumentation: 'OPT_OUT_INSTRUMENTATION',
appAutomationToken: 'SHOPIFY_APP_AUTOMATION_TOKEN',
Expand Down
6 changes: 1 addition & 5 deletions packages/cli-kit/src/private/node/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {isThemeAccessSession} from './api/rest.js'
import {getCurrentSessionId, setCurrentSessionId} from './conf-store.js'
import {UserEmailQueryString, UserEmailQuery} from './api/graphql/business-platform-destinations/user-email.js'
import {outputContent, outputToken, outputDebug, outputCompleted} from '../../public/node/output.js'
import {firstPartyDev, themeToken} from '../../public/node/context/local.js'
import {themeToken} from '../../public/node/context/local.js'
import {AbortError} from '../../public/node/error.js'
import {normalizeStoreFqdn, identityFqdn} from '../../public/node/context/fqdn.js'
import {getIdentityTokenInformation, getAppAutomationToken} from '../../public/node/environment.js'
Expand Down Expand Up @@ -296,10 +296,6 @@ async function executeCompleteFlow(applications: OAuthApplications, existingAlia
const scopes = getFlattenScopes(applications)
const exchangeScopes = getExchangeScopes(applications)
const store = applications.adminApi?.storeFqdn
if (firstPartyDev()) {
outputDebug(outputContent`Authenticating as Shopify Employee...`)
scopes.push('employee')
}

let identityToken: IdentityToken
const identityTokenInformation = getIdentityTokenInformation()
Expand Down
14 changes: 14 additions & 0 deletions packages/cli-kit/src/private/node/session/validate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,20 @@ describe('validateSession', () => {
expect(got).toBe('needs_full_auth')
})

test('returns needs_full_auth if cached identity has employee scope', async () => {
// Given
const session = {
identity: {...validIdentity, scopes: [...validIdentity.scopes, 'employee']},
applications: validApplications,
}

// When
const got = await validateSession(requestedScopes, defaultApps, session)

// Then
expect(got).toBe('needs_full_auth')
})

test('returns needs_refresh if identity is expired', async () => {
// Given
const session = {
Expand Down
Loading
Loading