diff --git a/packages/app/src/cli/models/app/app.ts b/packages/app/src/cli/models/app/app.ts index df707939e2b..5c6a5eab81c 100644 --- a/packages/app/src/cli/models/app/app.ts +++ b/packages/app/src/cli/models/app/app.ts @@ -432,18 +432,12 @@ export class App< } creationDefaultOptions(): CreateAppOptions { - const applicationUrl = this.configuration.application_url - const redirectUrls = this.configuration.auth?.redirect_urls - const staticRoot = this.configuration.admin?.static_root return { isLaunchable: this.appIsLaunchable(), scopesArray: getAppScopesArray(this.configuration), name: this.name, isEmbedded: this.appIsEmbedded, directory: this.directory, - applicationUrl, - redirectUrls, - staticRoot, } } diff --git a/packages/app/src/cli/models/app/loader.test.ts b/packages/app/src/cli/models/app/loader.test.ts index 23fba409baf..4f35ca534b0 100644 --- a/packages/app/src/cli/models/app/loader.test.ts +++ b/packages/app/src/cli/models/app/loader.test.ts @@ -3504,80 +3504,6 @@ value = true }) }) }) - - test('extracts application_url from template config', async () => { - await inTemporaryDirectory(async (tmpDir) => { - const config = ` -client_id = "" -name = "my-app" -application_url = "https://extensions.shopifycdn.com" -embedded = true - -[access_scopes] -scopes = "write_products" - -[auth] -redirect_urls = ["https://shopify.dev/apps/default-app-home/api/auth"] - ` - await writeFile(joinPath(tmpDir, 'shopify.app.toml'), config) - await writeFile(joinPath(tmpDir, 'package.json'), '{}') - - const result = await loadConfigForAppCreation(tmpDir, 'my-app') - - expect(result).toEqual({ - isLaunchable: false, - scopesArray: ['write_products'], - name: 'my-app', - directory: normalizePath(tmpDir), - isEmbedded: false, - applicationUrl: 'https://extensions.shopifycdn.com', - redirectUrls: ['https://shopify.dev/apps/default-app-home/api/auth'], - staticRoot: undefined, - }) - }) - }) - - test('extracts admin.static_root from template config', async () => { - await inTemporaryDirectory(async (tmpDir) => { - const config = ` -client_id = "" -name = "my-app" -application_url = "https://extensions.shopifycdn.com" -embedded = true - -[admin] -static_root = "./dist" - -[access_scopes] -scopes = "write_products" - ` - await writeFile(joinPath(tmpDir, 'shopify.app.toml'), config) - await writeFile(joinPath(tmpDir, 'package.json'), '{}') - - const result = await loadConfigForAppCreation(tmpDir, 'my-app') - - expect(result.staticRoot).toBe('./dist') - }) - }) - - test('defaults applicationUrl and redirectUrls to undefined when not in template config', async () => { - await inTemporaryDirectory(async (tmpDir) => { - const config = ` -client_id = "" -name = "my-app" - -[access_scopes] -scopes = "write_products" - ` - await writeFile(joinPath(tmpDir, 'shopify.app.toml'), config) - await writeFile(joinPath(tmpDir, 'package.json'), '{}') - - const result = await loadConfigForAppCreation(tmpDir, 'my-app') - - expect(result.applicationUrl).toBeUndefined() - expect(result.redirectUrls).toBeUndefined() - }) - }) }) describe('loadOpaqueApp', () => { diff --git a/packages/app/src/cli/models/app/loader.ts b/packages/app/src/cli/models/app/loader.ts index 285754067ca..1d6380929d7 100644 --- a/packages/app/src/cli/models/app/loader.ts +++ b/packages/app/src/cli/models/app/loader.ts @@ -206,10 +206,6 @@ export async function loadConfigForAppCreation(directory: string, name: string): const isLaunchable = webs.some((web) => isWebType(web, WebType.Frontend) || isWebType(web, WebType.Backend)) const scopesArray = getAppScopesArray(rawConfig as CurrentAppConfiguration) - const appConfig = rawConfig as CurrentAppConfiguration - const applicationUrl = appConfig.application_url - const redirectUrls = appConfig.auth?.redirect_urls - const staticRoot = appConfig.admin?.static_root return { isLaunchable, @@ -218,9 +214,6 @@ export async function loadConfigForAppCreation(directory: string, name: string): directory: project.directory, // By default, and ONLY for `app init`, we consider the app as embedded if it is launchable. isEmbedded: isLaunchable, - applicationUrl, - redirectUrls, - staticRoot, } } diff --git a/packages/app/src/cli/models/extensions/extension-instance.ts b/packages/app/src/cli/models/extensions/extension-instance.ts index 120e5b1fe47..3f40504ab1a 100644 --- a/packages/app/src/cli/models/extensions/extension-instance.ts +++ b/packages/app/src/cli/models/extensions/extension-instance.ts @@ -529,8 +529,6 @@ export class ExtensionInstance & BaseConfigType - -const adminSpecificationSpec = createExtensionSpecification({ - identifier: 'admin', - uidStrategy: 'single', - experience: 'configuration', - schema: AdminSchema as ZodSchemaType, - deployConfig: async (config, _) => { - return {admin: config.admin} - }, - devSessionWatchConfig: (extension) => { - const staticRoot = extension.configuration.admin?.static_root - if (!staticRoot) return {paths: []} - - const path = joinPath(extension.directory, staticRoot, '**/*') - return {paths: [path], ignore: []} - }, - transformRemoteToLocal: (remoteContent) => { - return { - admin: { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - static_root: (remoteContent as any).admin.static_root, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - allowed_domains: (remoteContent as any).admin.allowed_domains, - }, - } - }, - clientSteps: [ - { - lifecycle: 'deploy', - steps: [ - { - id: 'hosted_app_copy_files', - name: 'Hosted App Copy Files', - type: 'include_assets', - config: { - generatesAssetsManifest: true, - inclusions: [ - { - type: 'configKey', - key: 'admin.static_root', - }, - ], - }, - }, - ], - }, - ], - appModuleFeatures: () => [], -}) - -export default adminSpecificationSpec diff --git a/packages/app/src/cli/prompts/init/init.test.ts b/packages/app/src/cli/prompts/init/init.test.ts index bba278daa75..fa4c5bdfbfd 100644 --- a/packages/app/src/cli/prompts/init/init.test.ts +++ b/packages/app/src/cli/prompts/init/init.test.ts @@ -1,12 +1,10 @@ -import init, {buildNoneTemplate, InitOptions} from './init.js' +import init, {InitOptions} from './init.js' import {describe, expect, vi, test, beforeEach} from 'vitest' import {renderSelectPrompt} from '@shopify/cli-kit/node/ui' import {installGlobalCLIPrompt} from '@shopify/cli-kit/node/is-global' -import {isHostedAppsMode} from '@shopify/cli-kit/node/context/local' vi.mock('@shopify/cli-kit/node/ui') vi.mock('@shopify/cli-kit/node/is-global') -vi.mock('@shopify/cli-kit/node/context/local') const globalCLIResult = {install: true, alreadyInstalled: false} @@ -39,32 +37,6 @@ describe('init', () => { expect(got).toEqual({...options, ...answers, templateType: 'none', globalCLIResult}) }) - describe('buildNoneTemplate', () => { - test('returns hosted app label and URL when HOSTED_APPS is enabled', () => { - // Given - vi.mocked(isHostedAppsMode).mockReturnValue(true) - - // When - const got = buildNoneTemplate() - - // Then - expect(got.label).toBe('Build an extension-only app (Shopify-hosted Preact app home and extensions, no back-end)') - expect(got.url).toBe('https://github.com/Shopify/shopify-app-template-extension-only') - }) - - test('returns default label and URL when HOSTED_APPS is not set', () => { - // Given - vi.mocked(isHostedAppsMode).mockReturnValue(false) - - // When - const got = buildNoneTemplate() - - // Then - expect(got.label).toBe('Build an extension-only app') - expect(got.url).toBe('https://github.com/Shopify/shopify-app-template-none') - }) - }) - test('it renders branches for templates that have them', async () => { const answers = { template: 'https://github.com/Shopify/shopify-app-template-react-router#javascript-cli', diff --git a/packages/app/src/cli/prompts/init/init.ts b/packages/app/src/cli/prompts/init/init.ts index aa375b92447..1679d66260e 100644 --- a/packages/app/src/cli/prompts/init/init.ts +++ b/packages/app/src/cli/prompts/init/init.ts @@ -1,5 +1,4 @@ import {InstallGlobalCLIPromptResult, installGlobalCLIPrompt} from '@shopify/cli-kit/node/is-global' -import {isHostedAppsMode} from '@shopify/cli-kit/node/context/local' import {renderSelectPrompt} from '@shopify/cli-kit/node/ui' export interface InitOptions { @@ -29,19 +28,6 @@ interface Template { } } -export function buildNoneTemplate(): Template { - const hostedAppsEnabled = isHostedAppsMode() - return { - url: hostedAppsEnabled - ? 'https://github.com/Shopify/shopify-app-template-extension-only' - : 'https://github.com/Shopify/shopify-app-template-none', - label: hostedAppsEnabled - ? 'Build an extension-only app (Shopify-hosted Preact app home and extensions, no back-end)' - : 'Build an extension-only app', - visible: true, - } -} - // Eventually this list should be taken from a remote location // That way we don't have to update the CLI every time we add a template export const templates = { @@ -69,7 +55,11 @@ export const templates = { }, }, } as Template, - none: buildNoneTemplate(), + none: { + url: 'https://github.com/Shopify/shopify-app-template-none', + label: 'Build an extension-only app', + visible: true, + } as Template, node: { url: 'https://github.com/Shopify/shopify-app-template-node', visible: false, diff --git a/packages/app/src/cli/services/deploy/bundle.test.ts b/packages/app/src/cli/services/deploy/bundle.test.ts index 2ec28f886b7..184ee7cb2c4 100644 --- a/packages/app/src/cli/services/deploy/bundle.test.ts +++ b/packages/app/src/cli/services/deploy/bundle.test.ts @@ -6,16 +6,14 @@ import { testThemeExtensions, testUIExtension, } from '../../models/app/app.test-data.js' -import {AppInterface, AppManifest, WebType} from '../../models/app/app.js' +import {AppInterface, AppManifest} from '../../models/app/app.js' import * as bundle from '../bundle.js' import * as functionBuild from '../function/build.js' -import * as webService from '../web.js' import {describe, expect, test, vi} from 'vitest' import * as file from '@shopify/cli-kit/node/fs' import {joinPath} from '@shopify/cli-kit/node/path' vi.mock('../function/build.js') -vi.mock('../web.js') describe('bundleAndBuildExtensions', () => { let app: AppInterface @@ -262,153 +260,6 @@ describe('bundleAndBuildExtensions', () => { }) }) - test('runs web build command concurrently with extensions when build command is defined', async () => { - await file.inTemporaryDirectory(async (tmpDir: string) => { - // Given - const bundlePath = joinPath(tmpDir, 'bundle.zip') - const mockBuildWeb = vi.mocked(webService.default) - - const functionExtension = await testFunctionExtension() - const extensionBuildMock = vi.fn().mockImplementation(async (options, bundleDirectory) => { - file.writeFileSync(joinPath(bundleDirectory, 'index.wasm'), '') - }) - functionExtension.buildForBundle = extensionBuildMock - - const app = testApp({ - allExtensions: [functionExtension], - directory: tmpDir, - webs: [ - { - directory: '/tmp/web', - configuration: { - roles: [WebType.Backend], - commands: {dev: 'npm run dev', build: 'npm run build'}, - }, - }, - ], - }) - - const identifiers = { - app: 'app-id', - extensions: {[functionExtension.localIdentifier]: functionExtension.localIdentifier}, - extensionIds: {}, - extensionsNonUuidManaged: {}, - } - appManifest = await app.manifest(identifiers) - - // When - await bundleAndBuildExtensions({ - app, - appManifest, - identifiers, - bundlePath, - skipBuild: false, - isDevDashboardApp: false, - }) - - // Then - expect(mockBuildWeb).toHaveBeenCalledWith('build', expect.objectContaining({web: app.webs[0]})) - }) - }) - - test('skips web build for webs without a build command defined', async () => { - await file.inTemporaryDirectory(async (tmpDir: string) => { - // Given - const bundlePath = joinPath(tmpDir, 'bundle.zip') - const mockBuildWeb = vi.mocked(webService.default) - - const functionExtension = await testFunctionExtension() - const extensionBuildMock = vi.fn().mockImplementation(async (options, bundleDirectory) => { - file.writeFileSync(joinPath(bundleDirectory, 'index.wasm'), '') - }) - functionExtension.buildForBundle = extensionBuildMock - - const app = testApp({ - allExtensions: [functionExtension], - directory: tmpDir, - webs: [ - { - directory: '/tmp/web', - configuration: { - roles: [WebType.Backend], - commands: {dev: 'npm run dev'}, - }, - }, - ], - }) - - const identifiers = { - app: 'app-id', - extensions: {[functionExtension.localIdentifier]: functionExtension.localIdentifier}, - extensionIds: {}, - extensionsNonUuidManaged: {}, - } - appManifest = await app.manifest(identifiers) - - // When - await bundleAndBuildExtensions({ - app, - appManifest, - identifiers, - bundlePath, - skipBuild: false, - isDevDashboardApp: false, - }) - - // Then - expect(mockBuildWeb).not.toHaveBeenCalled() - }) - }) - - test('skips web build command when skipBuild is true', async () => { - await file.inTemporaryDirectory(async (tmpDir: string) => { - // Given - const bundlePath = joinPath(tmpDir, 'bundle.zip') - const mockBuildWeb = vi.mocked(webService.default) - - const functionExtension = await testFunctionExtension() - const extensionCopyMock = vi.fn().mockImplementation(async (options, bundleDirectory) => { - file.writeFileSync(joinPath(bundleDirectory, 'index.wasm'), '') - }) - functionExtension.copyIntoBundle = extensionCopyMock - - const app = testApp({ - allExtensions: [functionExtension], - directory: tmpDir, - webs: [ - { - directory: '/tmp/web', - configuration: { - roles: [WebType.Backend], - commands: {dev: 'npm run dev', build: 'npm run build'}, - }, - }, - ], - }) - - const identifiers = { - app: 'app-id', - extensions: {[functionExtension.localIdentifier]: functionExtension.localIdentifier}, - extensionIds: {}, - extensionsNonUuidManaged: {}, - } - appManifest = await app.manifest(identifiers) - - // When - await bundleAndBuildExtensions({ - app, - appManifest, - identifiers, - bundlePath, - skipBuild: true, - isDevDashboardApp: false, - }) - - // Then - expect(mockBuildWeb).not.toHaveBeenCalled() - }) - }) - test('handles multiple extension types together', async () => { await file.inTemporaryDirectory(async (tmpDir: string) => { // Given diff --git a/packages/app/src/cli/services/deploy/bundle.ts b/packages/app/src/cli/services/deploy/bundle.ts index 216645b51a0..e58d62c33b1 100644 --- a/packages/app/src/cli/services/deploy/bundle.ts +++ b/packages/app/src/cli/services/deploy/bundle.ts @@ -1,7 +1,6 @@ import {AppInterface, AppManifest} from '../../models/app/app.js' import {Identifiers} from '../../models/app/identifiers.js' import {installJavy} from '../function/build.js' -import buildWeb from '../web.js' import {compressBundle, writeManifestToBundle} from '../bundle.js' import {AbortSignal} from '@shopify/cli-kit/node/abort' import {mkdir, rmdir} from '@shopify/cli-kit/node/fs' @@ -37,18 +36,6 @@ export async function bundleAndBuildExtensions(options: BundleOptions): Promise< await installJavy(options.app) } - const webBuildProcesses = options.skipBuild - ? [] - : options.app.webs - .filter((web) => web.configuration.commands.build) - .map((web) => ({ - prefix: ['web', ...web.configuration.roles].join('-'), - action: async (stdout: Writable, stderr: Writable, signal: AbortSignal) => { - if (options.skipBuild) return - await buildWeb('build', {web, stdout, stderr, signal}) - }, - })) - const extensionBuildProcesses = options.app.allExtensions.map((extension) => ({ prefix: extension.localIdentifier, action: async (stdout: Writable, stderr: Writable, signal: AbortSignal) => { @@ -75,7 +62,7 @@ export async function bundleAndBuildExtensions(options: BundleOptions): Promise< })) await renderConcurrent({ - processes: [webBuildProcesses, extensionBuildProcesses].flat(), + processes: extensionBuildProcesses, showTimestamps: false, }) diff --git a/packages/app/src/cli/services/dev/extension.test.ts b/packages/app/src/cli/services/dev/extension.test.ts index f63ce543464..f7fa2a6e95f 100644 --- a/packages/app/src/cli/services/dev/extension.test.ts +++ b/packages/app/src/cli/services/dev/extension.test.ts @@ -37,8 +37,6 @@ describe('devUIExtensions()', () => { () => ({ mock: 'payload-store', - updateAdminConfigFromExtensionEvents: vi.fn(), - getAppAssets: vi.fn(), }) as unknown as store.ExtensionsPayloadStore, ) vi.spyOn(server, 'setupHTTPServer').mockReturnValue({ @@ -76,7 +74,6 @@ describe('devUIExtensions()', () => { devOptions: {...options, websocketURL: 'wss://mock.url/extensions'}, payloadStore: expect.objectContaining({mock: 'payload-store'}), getExtensions: expect.any(Function), - getAppAssets: expect.any(Function), }) }) diff --git a/packages/app/src/cli/services/dev/extension.ts b/packages/app/src/cli/services/dev/extension.ts index 91eab7eb2ba..c594e74384d 100644 --- a/packages/app/src/cli/services/dev/extension.ts +++ b/packages/app/src/cli/services/dev/extension.ts @@ -134,12 +134,10 @@ export async function devUIExtensions(options: ExtensionDevOptions): Promise payloadStore.getAppAssets() const httpServer = setupHTTPServer({ devOptions: payloadOptions, payloadStore, getExtensions, - getAppAssets, }) outputDebug(`Setting up the UI extensions Websocket server...`, payloadOptions.stdout) @@ -151,11 +149,6 @@ export async function devUIExtensions(options: ExtensionDevOptions): Promise ext.isPreviewable) } - // Exception for AdminConfig: it's a extension that needs its config extracted at the app level - // for the payloed. No other exceptions should be added, if this pattern is needed again we should - // generalize it. - payloadStore.updateAdminConfigFromExtensionEvents(extensionEvents) - for (const event of extensionEvents) { if (!event.extension.isPreviewable) continue const status = event.buildResult?.status === 'ok' ? 'success' : 'error' diff --git a/packages/app/src/cli/services/dev/extension/payload/models.ts b/packages/app/src/cli/services/dev/extension/payload/models.ts index 753a6511c0b..2755d152a55 100644 --- a/packages/app/src/cli/services/dev/extension/payload/models.ts +++ b/packages/app/src/cli/services/dev/extension/payload/models.ts @@ -8,13 +8,6 @@ interface ExtensionsPayloadInterface { url: string mobileUrl: string title: string - allowedDomains?: string[] - assets?: { - [key: string]: { - url: string - lastUpdated: number - } - } } appId?: string store: string diff --git a/packages/app/src/cli/services/dev/extension/payload/store.test.ts b/packages/app/src/cli/services/dev/extension/payload/store.test.ts index fdc460d2d4a..d1734d2b4cd 100644 --- a/packages/app/src/cli/services/dev/extension/payload/store.test.ts +++ b/packages/app/src/cli/services/dev/extension/payload/store.test.ts @@ -7,18 +7,8 @@ import { import {UIExtensionPayload, ExtensionsEndpointPayload} from './models.js' import * as payload from '../payload.js' import {ExtensionInstance} from '../../../../models/extensions/extension-instance.js' -import {ExtensionEvent} from '../../app-events/app-event-watcher.js' import {beforeEach, describe, expect, test, vi} from 'vitest' -function createAdminExtension(config: {static_root?: string; allowed_domains?: string[]} = {}) { - return { - type: 'admin', - isPreviewable: false, - configuration: {admin: config}, - specification: {}, - } as unknown as ExtensionInstance -} - describe('getExtensionsPayloadStoreRawPayload()', () => { test('returns the raw payload', async () => { // Given @@ -66,104 +56,6 @@ describe('getExtensionsPayloadStoreRawPayload()', () => { extensions: [{mock: 'extension-payload'}, {mock: 'extension-payload'}, {mock: 'extension-payload'}], }) }) - - test('includes allowed_domains and assets when admin extension is present', async () => { - // Given - vi.spyOn(payload, 'getUIExtensionPayload').mockResolvedValue({mock: 'ext'} as unknown as UIExtensionPayload) - const adminExt = createAdminExtension({static_root: 'public', allowed_domains: ['https://cdn.example.com']}) - const previewableExt = {specification: {}, isPreviewable: true} as unknown as ExtensionInstance - - const options = { - apiKey: 'api-key', - appName: 'my-app', - url: 'https://tunnel.example.com', - websocketURL: 'wss://tunnel.example.com', - extensions: [previewableExt, adminExt], - storeFqdn: 'store.myshopify.com', - manifestVersion: '3', - } as unknown as ExtensionsPayloadStoreOptions - - // When - const rawPayload = await getExtensionsPayloadStoreRawPayload(options, 'bundle-path') - - // Then - expect(rawPayload.app.allowedDomains).toStrictEqual(['https://cdn.example.com']) - expect(rawPayload.app.assets).toStrictEqual({ - staticRoot: { - url: 'https://tunnel.example.com/extensions/assets/staticRoot/', - lastUpdated: expect.any(Number), - }, - }) - // Admin extension should not appear in the UI extensions payload - expect(rawPayload.extensions).toHaveLength(1) - }) - - test('does not include assets or allowed_domains when no admin extension exists', async () => { - // Given - vi.spyOn(payload, 'getUIExtensionPayload').mockResolvedValue({mock: 'ext'} as unknown as UIExtensionPayload) - - const options = { - apiKey: 'api-key', - appName: 'my-app', - url: 'https://tunnel.example.com', - websocketURL: 'wss://tunnel.example.com', - extensions: [{specification: {}, isPreviewable: true}], - storeFqdn: 'store.myshopify.com', - manifestVersion: '3', - } as unknown as ExtensionsPayloadStoreOptions - - // When - const rawPayload = await getExtensionsPayloadStoreRawPayload(options, 'bundle-path') - - // Then - expect(rawPayload.app.allowedDomains).toBeUndefined() - expect(rawPayload.app.assets).toBeUndefined() - }) - - test('includes allowed_domains but not assets when admin has no static_root', async () => { - // Given - vi.spyOn(payload, 'getUIExtensionPayload').mockResolvedValue({mock: 'ext'} as unknown as UIExtensionPayload) - const adminExt = createAdminExtension({allowed_domains: ['https://cdn.example.com']}) - - const options = { - apiKey: 'api-key', - appName: 'my-app', - url: 'https://tunnel.example.com', - websocketURL: 'wss://tunnel.example.com', - extensions: [adminExt], - storeFqdn: 'store.myshopify.com', - manifestVersion: '3', - } as unknown as ExtensionsPayloadStoreOptions - - // When - const rawPayload = await getExtensionsPayloadStoreRawPayload(options, 'bundle-path') - - // Then - expect(rawPayload.app.allowedDomains).toStrictEqual(['https://cdn.example.com']) - expect(rawPayload.app.assets).toBeUndefined() - }) - - test('defaults allowed_domains to empty array when admin has no allowed_domains configured', async () => { - // Given - vi.spyOn(payload, 'getUIExtensionPayload').mockResolvedValue({mock: 'ext'} as unknown as UIExtensionPayload) - const adminExt = createAdminExtension({}) - - const options = { - apiKey: 'api-key', - appName: 'my-app', - url: 'https://tunnel.example.com', - websocketURL: 'wss://tunnel.example.com', - extensions: [adminExt], - storeFqdn: 'store.myshopify.com', - manifestVersion: '3', - } as unknown as ExtensionsPayloadStoreOptions - - // When - const rawPayload = await getExtensionsPayloadStoreRawPayload(options, 'bundle-path') - - // Then - expect(rawPayload.app.allowedDomains).toStrictEqual([]) - }) }) describe('ExtensionsPayloadStore()', () => { @@ -524,95 +416,4 @@ describe('ExtensionsPayloadStore()', () => { expect(onUpdateSpy).not.toHaveBeenCalled() }) }) - - describe('getAppAssets()', () => { - test('returns asset directories when admin extension has static_root', () => { - const adminExt = createAdminExtension({static_root: 'public'}) - const options = {extensions: [adminExt], appDirectory: '/app'} as unknown as ExtensionsPayloadStoreOptions - const store = new ExtensionsPayloadStore({extensions: []} as unknown as ExtensionsEndpointPayload, options) - - expect(store.getAppAssets()).toStrictEqual({staticRoot: '/app/public'}) - }) - - test('returns undefined when no admin extension exists', () => { - const store = new ExtensionsPayloadStore({extensions: []} as unknown as ExtensionsEndpointPayload, mockOptions) - - expect(store.getAppAssets()).toBeUndefined() - }) - - test('returns undefined when admin extension has no static_root', () => { - const adminExt = createAdminExtension({allowed_domains: ['https://example.com']}) - const options = {extensions: [adminExt], appDirectory: '/app'} as unknown as ExtensionsPayloadStoreOptions - const store = new ExtensionsPayloadStore({extensions: []} as unknown as ExtensionsEndpointPayload, options) - - expect(store.getAppAssets()).toBeUndefined() - }) - }) - - describe('updateAdminConfigFromExtensionEvents()', () => { - test('updates allowed_domains and bumps asset timestamps on admin change', () => { - // Given - const adminExt = createAdminExtension({allowed_domains: ['https://new.example.com'], static_root: 'public'}) - const options = {extensions: [adminExt], appDirectory: '/app'} as unknown as ExtensionsPayloadStoreOptions - const initialPayload = { - app: { - allowed_domains: ['https://old.example.com'], - assets: {staticRoot: {url: 'https://tunnel/extensions/assets/staticRoot/', lastUpdated: 1000}}, - }, - extensions: [], - } as unknown as ExtensionsEndpointPayload - - const store = new ExtensionsPayloadStore(initialPayload, options) - const onUpdateSpy = vi.fn() - store.on(ExtensionsPayloadStoreEvent.Update, onUpdateSpy) - - // When - store.updateAdminConfigFromExtensionEvents([{extension: adminExt} as unknown as ExtensionEvent]) - - // Then - const result = store.getRawPayload() - expect(result.app.allowedDomains).toStrictEqual(['https://new.example.com']) - expect(result.app.assets!.staticRoot!.lastUpdated).toBeGreaterThan(1000) - expect(onUpdateSpy).toHaveBeenCalledWith([]) - }) - - test('clears allowed_domains when admin config removes them', () => { - // Given - const adminExt = createAdminExtension({static_root: 'public'}) - const options = {extensions: [adminExt], appDirectory: '/app'} as unknown as ExtensionsPayloadStoreOptions - const initialPayload = { - app: {allowedDomains: ['https://old.example.com'], assets: {}}, - extensions: [], - } as unknown as ExtensionsEndpointPayload - - const store = new ExtensionsPayloadStore(initialPayload, options) - - // When - store.updateAdminConfigFromExtensionEvents([{extension: adminExt} as unknown as ExtensionEvent]) - - // Then - expect(store.getRawPayload().app.allowedDomains).toStrictEqual([]) - }) - - test('does nothing when no admin extension event is present', () => { - // Given - const store = new ExtensionsPayloadStore( - {app: {allowedDomains: ['https://example.com']}, extensions: []} as unknown as ExtensionsEndpointPayload, - mockOptions, - ) - const onUpdateSpy = vi.fn() - store.on(ExtensionsPayloadStoreEvent.Update, onUpdateSpy) - - const nonAdminEvent = { - extension: {type: 'ui_extension'}, - } as unknown as ExtensionEvent - - // When - store.updateAdminConfigFromExtensionEvents([nonAdminEvent]) - - // Then - expect(store.getRawPayload().app.allowedDomains).toStrictEqual(['https://example.com']) - expect(onUpdateSpy).not.toHaveBeenCalled() - }) - }) }) diff --git a/packages/app/src/cli/services/dev/extension/payload/store.ts b/packages/app/src/cli/services/dev/extension/payload/store.ts index fa0af94b97b..0af0eb7e2e8 100644 --- a/packages/app/src/cli/services/dev/extension/payload/store.ts +++ b/packages/app/src/cli/services/dev/extension/payload/store.ts @@ -3,9 +3,6 @@ import {ExtensionDevOptions} from '../../extension.js' import {getUIExtensionPayload, isNewExtensionPointsSchema} from '../payload.js' import {buildAppURLForMobile, buildAppURLForWeb} from '../../../../utilities/app/app-url.js' import {ExtensionInstance} from '../../../../models/extensions/extension-instance.js' -import {AdminConfigType} from '../../../../models/extensions/specifications/admin.js' -import {ExtensionEvent} from '../../app-events/app-event-watcher.js' -import {joinPath} from '@shopify/cli-kit/node/path' import {deepMergeObjects} from '@shopify/cli-kit/common/object' import {outputDebug, outputContent} from '@shopify/cli-kit/node/output' import {EventEmitter} from 'events' @@ -14,21 +11,6 @@ export interface ExtensionsPayloadStoreOptions extends ExtensionDevOptions { websocketURL: string } -interface AdminConfig { - allowedDomains: string[] - staticRoot?: string -} - -function getAdminConfig(extensions: ExtensionInstance[]): AdminConfig | undefined { - const adminExtension = extensions.find((ext) => ext.type === 'admin') - if (!adminExtension) return undefined - const admin = (adminExtension.configuration as AdminConfigType).admin - return { - allowedDomains: admin?.allowed_domains ?? [], - staticRoot: admin?.static_root, - } -} - export enum ExtensionsPayloadStoreEvent { Update = 'PayloadUpdatedEvent:UPDATE', } @@ -63,39 +45,17 @@ export async function getExtensionsPayloadStoreRawPayload( ), } - // Admin extension contributes app-level config to the payload - const adminConfig = getAdminConfig(options.extensions) - if (adminConfig) { - payload.app.allowedDomains = adminConfig.allowedDomains - if (adminConfig.staticRoot) { - const assetKey = 'staticRoot' - payload.app.assets = { - [assetKey]: { - url: new URL(`/extensions/assets/${assetKey}/`, options.url).toString(), - lastUpdated: Date.now(), - }, - } - } - } - return payload } export class ExtensionsPayloadStore extends EventEmitter { private readonly options: ExtensionsPayloadStoreOptions private rawPayload: ExtensionsEndpointPayload - private appAssetDirectories: Record | undefined constructor(rawPayload: ExtensionsEndpointPayload, options: ExtensionsPayloadStoreOptions) { super() this.rawPayload = rawPayload this.options = options - - this.refreshAppAssetDirectories() - } - - getAppAssets(): Record | undefined { - return this.appAssetDirectories } getConnectedPayload() { @@ -216,28 +176,6 @@ export class ExtensionsPayloadStore extends EventEmitter { this.emitUpdate([extension.devUUID]) } - updateAdminConfigFromExtensionEvents(extensionEvents: ExtensionEvent[]) { - const adminConfig = getAdminConfig(extensionEvents.map((event) => event.extension)) - if (!adminConfig) return - this.rawPayload.app.allowedDomains = adminConfig.allowedDomains - - this.refreshAppAssetDirectories() - if (this.rawPayload.app.assets) { - for (const key of Object.keys(this.rawPayload.app.assets)) { - this.rawPayload.app.assets[key]!.lastUpdated = Date.now() - } - } - - this.emitUpdate([]) - } - - private refreshAppAssetDirectories() { - const adminConfig = getAdminConfig(this.options.extensions) - this.appAssetDirectories = adminConfig?.staticRoot - ? {staticRoot: joinPath(this.options.appDirectory, adminConfig.staticRoot)} - : undefined - } - private emitUpdate(extensionIds: string[]) { this.emit(ExtensionsPayloadStoreEvent.Update, extensionIds) } diff --git a/packages/app/src/cli/services/dev/extension/server.ts b/packages/app/src/cli/services/dev/extension/server.ts index b7a5bd7adec..456c8364c61 100644 --- a/packages/app/src/cli/services/dev/extension/server.ts +++ b/packages/app/src/cli/services/dev/extension/server.ts @@ -2,7 +2,6 @@ import { corsMiddleware, devConsoleAssetsMiddleware, devConsoleIndexMiddleware, - getAppAssetsMiddleware, getExtensionAssetMiddleware, getExtensionPayloadMiddleware, getExtensionPointMiddleware, @@ -20,7 +19,6 @@ interface SetupHTTPServerOptions { devOptions: ExtensionsPayloadStoreOptions payloadStore: ExtensionsPayloadStore getExtensions: () => ExtensionInstance[] - getAppAssets?: () => Record | undefined } export function setupHTTPServer(options: SetupHTTPServerOptions) { @@ -30,9 +28,6 @@ export function setupHTTPServer(options: SetupHTTPServerOptions) { httpApp.use(getLogMiddleware(options)) httpApp.use(corsMiddleware) httpApp.use(noCacheMiddleware) - if (options.getAppAssets) { - httpRouter.use('/extensions/assets/:assetKey/**:filePath', getAppAssetsMiddleware(options.getAppAssets)) - } httpRouter.use('/extensions/dev-console', devConsoleIndexMiddleware) httpRouter.use('/extensions/dev-console/assets/**:assetPath', devConsoleAssetsMiddleware) httpRouter.use('/extensions/:extensionId', getExtensionPayloadMiddleware(options)) diff --git a/packages/app/src/cli/services/dev/extension/server/middlewares.ts b/packages/app/src/cli/services/dev/extension/server/middlewares.ts index a205ab4a188..859a5d68626 100644 --- a/packages/app/src/cli/services/dev/extension/server/middlewares.ts +++ b/packages/app/src/cli/services/dev/extension/server/middlewares.ts @@ -143,25 +143,6 @@ export const devConsoleAssetsMiddleware = defineEventHandler(async (event) => { }) }) -export function getAppAssetsMiddleware(getAppAssets: () => Record | undefined) { - return defineEventHandler(async (event) => { - const {assetKey = '', filePath = ''} = getRouterParams(event) - const appAssets = getAppAssets() - const directory = appAssets?.[assetKey] - if (!directory) { - return sendError(event, {statusCode: 404, statusMessage: `No app assets configured for key: ${assetKey}`}) - } - const resolvedDirectory = resolvePath(directory) - const resolvedFilePath = resolvePath(directory, filePath) - if (!resolvedFilePath.startsWith(resolvedDirectory)) { - return sendError(event, {statusCode: 403, statusMessage: 'Path traversal is not allowed'}) - } - return fileServerMiddleware(event, { - filePath: resolvedFilePath, - }) - }) -} - export function getLogMiddleware({devOptions}: GetExtensionsMiddlewareOptions) { return defineEventHandler((event) => { outputDebug(`UI extensions server received a ${event.method} request to URL ${event.path}`, devOptions.stdout) diff --git a/packages/app/src/cli/utilities/developer-platform-client.ts b/packages/app/src/cli/utilities/developer-platform-client.ts index 30376507f30..e8d792f0de1 100644 --- a/packages/app/src/cli/utilities/developer-platform-client.ts +++ b/packages/app/src/cli/utilities/developer-platform-client.ts @@ -120,9 +120,6 @@ export interface CreateAppOptions { scopesArray?: string[] directory?: string isEmbedded?: boolean - applicationUrl?: string - redirectUrls?: string[] - staticRoot?: string } interface AppModuleVersionSpecification { diff --git a/packages/app/src/cli/utilities/developer-platform-client/app-management-client.test.ts b/packages/app/src/cli/utilities/developer-platform-client/app-management-client.test.ts index a9c636c4b0f..5e530f3c72f 100644 --- a/packages/app/src/cli/utilities/developer-platform-client/app-management-client.test.ts +++ b/packages/app/src/cli/utilities/developer-platform-client/app-management-client.test.ts @@ -652,140 +652,6 @@ describe('createApp', () => { expect(result).toMatchObject(expectedApp) }) - test('uses applicationUrl and redirectUrls from options when provided', async () => { - // Given - const client = AppManagementClient.getInstance() - const org = testOrganization() - vi.mocked(webhooksRequestDoc).mockResolvedValueOnce({ - publicApiVersions: [{handle: '2024-07'}, {handle: '2024-10'}, {handle: '2025-01'}, {handle: 'unstable'}], - }) - vi.mocked(appManagementRequestDoc).mockResolvedValueOnce({ - appCreate: { - app: {id: '1', key: 'key', activeRoot: {clientCredentials: {secrets: [{key: 'secret'}]}}}, - userErrors: [], - }, - }) - - // When - client.token = () => Promise.resolve('token') - await client.createApp(org, { - name: 'app-name', - isLaunchable: false, - applicationUrl: 'https://extensions.shopifycdn.com', - redirectUrls: ['https://shopify.dev/apps/default-app-home/api/auth'], - }) - - // Then - expect(vi.mocked(appManagementRequestDoc)).toHaveBeenCalledWith({ - query: CreateApp, - token: 'token', - variables: { - organizationId: 'gid://shopify/Organization/1', - initialVersion: { - source: { - name: 'app-name', - modules: expect.arrayContaining([ - { - type: 'app_home', - config: { - app_url: 'https://extensions.shopifycdn.com', - embedded: true, - }, - }, - { - type: 'app_access', - config: { - redirect_url_allowlist: ['https://shopify.dev/apps/default-app-home/api/auth'], - }, - }, - ]), - }, - }, - }, - unauthorizedHandler: { - handler: expect.any(Function), - type: 'token_refresh', - }, - }) - }) - - test('includes admin module with static_root when staticRoot is provided', async () => { - // Given - const client = AppManagementClient.getInstance() - const org = testOrganization() - vi.mocked(webhooksRequestDoc).mockResolvedValueOnce({ - publicApiVersions: [{handle: '2024-07'}, {handle: '2024-10'}, {handle: '2025-01'}, {handle: 'unstable'}], - }) - vi.mocked(appManagementRequestDoc).mockResolvedValueOnce({ - appCreate: { - app: {id: '1', key: 'key', activeRoot: {clientCredentials: {secrets: [{key: 'secret'}]}}}, - userErrors: [], - }, - }) - - // When - client.token = () => Promise.resolve('token') - await client.createApp(org, { - name: 'app-name', - isLaunchable: false, - applicationUrl: 'https://extensions.shopifycdn.com', - staticRoot: './dist', - }) - - // Then - expect(vi.mocked(appManagementRequestDoc)).toHaveBeenCalledWith( - expect.objectContaining({ - variables: expect.objectContaining({ - initialVersion: expect.objectContaining({ - source: expect.objectContaining({ - modules: expect.arrayContaining([ - { - type: 'admin', - config: {admin: {static_root: './dist'}}, - }, - ]), - }), - }), - }), - }), - ) - }) - - test('does not include admin module when staticRoot is not provided', async () => { - // Given - const client = AppManagementClient.getInstance() - const org = testOrganization() - vi.mocked(webhooksRequestDoc).mockResolvedValueOnce({ - publicApiVersions: [{handle: '2024-07'}, {handle: '2024-10'}, {handle: '2025-01'}, {handle: 'unstable'}], - }) - vi.mocked(appManagementRequestDoc).mockResolvedValueOnce({ - appCreate: { - app: {id: '1', key: 'key', activeRoot: {clientCredentials: {secrets: [{key: 'secret'}]}}}, - userErrors: [], - }, - }) - - // When - client.token = () => Promise.resolve('token') - await client.createApp(org, { - name: 'app-name', - isLaunchable: false, - }) - - // Then - expect(vi.mocked(appManagementRequestDoc)).toHaveBeenCalledWith( - expect.objectContaining({ - variables: expect.objectContaining({ - initialVersion: expect.objectContaining({ - source: expect.objectContaining({ - modules: expect.not.arrayContaining([expect.objectContaining({type: 'admin'})]), - }), - }), - }), - }), - ) - }) - test('sets embedded to true in app home module', async () => { // Given const client = AppManagementClient.getInstance() diff --git a/packages/app/src/cli/utilities/developer-platform-client/app-management-client.ts b/packages/app/src/cli/utilities/developer-platform-client/app-management-client.ts index 89e4cdc7a9c..e42b2cad9e6 100644 --- a/packages/app/src/cli/utilities/developer-platform-client/app-management-client.ts +++ b/packages/app/src/cli/utilities/developer-platform-client/app-management-client.ts @@ -82,8 +82,6 @@ import {ListOrganizations} from '../../api/graphql/business-platform-destination import {AppHomeSpecIdentifier} from '../../models/extensions/specifications/app_config_app_home.js' import {BrandingSpecIdentifier} from '../../models/extensions/specifications/app_config_branding.js' import {AppAccessSpecIdentifier} from '../../models/extensions/specifications/app_config_app_access.js' -import {AdminSpecIdentifier} from '../../models/extensions/specifications/admin.js' - import {DevSessionCreate, DevSessionCreateMutation} from '../../api/graphql/app-dev/generated/dev-session-create.js' import { DevSessionUpdate, @@ -1224,7 +1222,7 @@ function createAppVars( { type: AppHomeSpecIdentifier, config: { - app_url: options.applicationUrl ?? defaultAppUrl, + app_url: defaultAppUrl, // Ext-only apps should be embedded = false, however we are hardcoding this to // match Partners behaviour for now // https://github.com/Shopify/develop-app-inner-loop/issues/2789 @@ -1242,18 +1240,10 @@ function createAppVars( { type: AppAccessSpecIdentifier, config: { - redirect_url_allowlist: options.redirectUrls ?? [defaultRedirectUrl], + redirect_url_allowlist: [defaultRedirectUrl], ...(scopesArray && {scopes: scopesArray.map((scope) => scope.trim()).join(',')}), }, }, - ...(options.staticRoot - ? [ - { - type: AdminSpecIdentifier, - config: {admin: {static_root: options.staticRoot}}, - }, - ] - : []), ], }, } diff --git a/packages/app/src/cli/utilities/developer-platform-client/partners-client.test.ts b/packages/app/src/cli/utilities/developer-platform-client/partners-client.test.ts index e4e1d260ae0..e2cce11bf64 100644 --- a/packages/app/src/cli/utilities/developer-platform-client/partners-client.test.ts +++ b/packages/app/src/cli/utilities/developer-platform-client/partners-client.test.ts @@ -152,39 +152,6 @@ describe('createApp', () => { }) }) - test('uses applicationUrl and redirectUrls from options when provided', async () => { - // Given - const partnersClient = PartnersClient.getInstance(testPartnersUserSession) - vi.mocked(appNamePrompt).mockResolvedValue('app-name') - vi.mocked(partnersRequest).mockResolvedValueOnce({appCreate: {app: APP1, userErrors: []}}) - const variables = { - org: 1, - title: LOCAL_APP.name, - appUrl: 'https://extensions.shopifycdn.com', - redir: ['https://shopify.dev/apps/default-app-home/api/auth'], - requestedAccessScopes: ['write_products'], - type: 'undecided', - } - - // When - await partnersClient.createApp( - {...ORG1, source: OrganizationSource.Partners}, - { - name: LOCAL_APP.name, - isLaunchable: false, - scopesArray: ['write_products'], - applicationUrl: 'https://extensions.shopifycdn.com', - redirectUrls: ['https://shopify.dev/apps/default-app-home/api/auth'], - }, - ) - - // Then - expect(partnersRequest).toHaveBeenCalledWith(CreateAppQuery, 'token', variables, undefined, undefined, { - type: 'token_refresh', - handler: expect.any(Function), - }) - }) - test('throws error if requests has a user error', async () => { // Given const partnersClient = PartnersClient.getInstance(testPartnersUserSession) diff --git a/packages/app/src/cli/utilities/developer-platform-client/partners-client.ts b/packages/app/src/cli/utilities/developer-platform-client/partners-client.ts index 6834829b4c7..57e142a35a9 100644 --- a/packages/app/src/cli/utilities/developer-platform-client/partners-client.ts +++ b/packages/app/src/cli/utilities/developer-platform-client/partners-client.ts @@ -178,8 +178,8 @@ function getAppVars(org: Organization, options: CreateAppOptions): CreateAppQuer return { org: parseInt(org.id, 10), title: name, - appUrl: options.applicationUrl ?? defaultAppUrl, - redir: options.redirectUrls ?? [defaultRedirectUrl], + appUrl: defaultAppUrl, + redir: [defaultRedirectUrl], requestedAccessScopes: scopesArray ?? [], type: 'undecided', } diff --git a/packages/cli-kit/src/private/node/constants.ts b/packages/cli-kit/src/private/node/constants.ts index 40417176c61..50e3d05b547 100644 --- a/packages/cli-kit/src/private/node/constants.ts +++ b/packages/cli-kit/src/private/node/constants.ts @@ -48,7 +48,6 @@ export const environmentVariables = { skipNetworkLevelRetry: 'SHOPIFY_CLI_SKIP_NETWORK_LEVEL_RETRY', maxRequestTimeForNetworkCalls: 'SHOPIFY_CLI_MAX_REQUEST_TIME_FOR_NETWORK_CALLS', disableImportScanning: 'SHOPIFY_CLI_DISABLE_IMPORT_SCANNING', - hostedApps: 'HOSTED_APPS', } export const defaultThemeKitAccessDomain = 'theme-kit-access.shopifyapps.com' diff --git a/packages/cli-kit/src/public/node/context/local.test.ts b/packages/cli-kit/src/public/node/context/local.test.ts index a4116f8cea9..65de0794585 100644 --- a/packages/cli-kit/src/public/node/context/local.test.ts +++ b/packages/cli-kit/src/public/node/context/local.test.ts @@ -2,7 +2,6 @@ import { ciPlatform, hasGit, isDevelopment, - isHostedAppsMode, isShopify, isTerminalInteractive, isUnitTest, @@ -97,30 +96,6 @@ describe('isDevelopment', () => { }) }) -describe('isHostedAppsMode', () => { - test('returns true when HOSTED_APPS is truthy', () => { - // Given - const env = {HOSTED_APPS: '1'} - - // When - const got = isHostedAppsMode(env) - - // Then - expect(got).toBe(true) - }) - - test('returns false when HOSTED_APPS is not set', () => { - // Given - const env = {} - - // When - const got = isHostedAppsMode(env) - - // Then - expect(got).toBe(false) - }) -}) - describe('isShopify', () => { test('returns false when the SHOPIFY_RUN_AS_USER env. variable is truthy', async () => { // Given diff --git a/packages/cli-kit/src/public/node/context/local.ts b/packages/cli-kit/src/public/node/context/local.ts index 34fddf6791f..855e0d7e50e 100644 --- a/packages/cli-kit/src/public/node/context/local.ts +++ b/packages/cli-kit/src/public/node/context/local.ts @@ -46,16 +46,6 @@ export function isVerbose(env = process.env): boolean { return isTruthy(env[environmentVariables.verbose]) || process.argv.includes('--verbose') } -/** - * Returns true if the hosted apps mode is enabled. - * - * @param env - The environment variables from the environment of the current process. - * @returns True if HOSTED_APPS is truthy. - */ -export function isHostedAppsMode(env = process.env): boolean { - return isTruthy(env[environmentVariables.hostedApps]) -} - /** * Returns true if the environment in which the CLI is running is either * a local environment (where dev is present).