diff --git a/packages/app/src/cli/models/extensions/specifications/admin.test.ts b/packages/app/src/cli/models/extensions/specifications/admin.test.ts new file mode 100644 index 00000000000..f9af0bba308 --- /dev/null +++ b/packages/app/src/cli/models/extensions/specifications/admin.test.ts @@ -0,0 +1,83 @@ +import adminSpec, {AdminConfigType} from './admin.js' +import {ExtensionInstance} from '../extension-instance.js' +import {inTemporaryDirectory, mkdir, touchFile} from '@shopify/cli-kit/node/fs' +import {joinPath} from '@shopify/cli-kit/node/path' +import {describe, expect, test} from 'vitest' + +function createAdminExtensionInstance(directory: string, staticRoot?: string): ExtensionInstance { + return { + directory, + configuration: { + name: 'test-admin', + type: 'admin', + admin: staticRoot ? {static_root: staticRoot} : undefined, + }, + } as unknown as ExtensionInstance +} + +describe('admin buildValidation', () => { + test('passes when static_root is not configured', async () => { + await inTemporaryDirectory(async (tmpDir) => { + const extension = createAdminExtensionInstance(tmpDir, undefined) + + // Should not throw + await expect(adminSpec.buildValidation!(extension)).resolves.toBeUndefined() + }) + }) + + test('passes when static_root exists and contains index.html', async () => { + await inTemporaryDirectory(async (tmpDir) => { + const distDir = joinPath(tmpDir, 'dist') + await mkdir(distDir) + await touchFile(joinPath(distDir, 'index.html')) + + const extension = createAdminExtensionInstance(tmpDir, './dist') + + // Should not throw + await expect(adminSpec.buildValidation!(extension)).resolves.toBeUndefined() + }) + }) + + test('throws when static_root is configured but index.html is missing', async () => { + await inTemporaryDirectory(async (tmpDir) => { + // Create the dist directory but without index.html + const distDir = joinPath(tmpDir, 'dist') + await mkdir(distDir) + + const extension = createAdminExtensionInstance(tmpDir, './dist') + + await expect(adminSpec.buildValidation!(extension)).rejects.toThrow( + 'The admin extension requires an index.html file in the static_root directory (./dist), but it was not found.', + ) + }) + }) + + test('throws when static_root directory does not exist', async () => { + await inTemporaryDirectory(async (tmpDir) => { + const extension = createAdminExtensionInstance(tmpDir, './dist') + + await expect(adminSpec.buildValidation!(extension)).rejects.toThrow( + 'The admin extension requires an index.html file in the static_root directory (./dist), but it was not found.', + ) + }) + }) +}) + +describe('admin devSessionWatchConfig', () => { + test('returns empty paths when static_root is not configured', () => { + const extension = createAdminExtensionInstance('/tmp/test', undefined) + const watchConfig = adminSpec.devSessionWatchConfig!(extension) + + expect(watchConfig).toEqual({paths: []}) + }) + + test('returns watch paths when static_root is configured', () => { + const extension = createAdminExtensionInstance('/tmp/test', './dist') + const watchConfig = adminSpec.devSessionWatchConfig!(extension) + + expect(watchConfig).toBeDefined() + expect(watchConfig!.paths).toHaveLength(1) + expect(watchConfig!.paths[0]).toContain('dist') + expect(watchConfig!.paths[0]).toContain('**/*') + }) +}) diff --git a/packages/app/src/cli/models/extensions/specifications/admin.ts b/packages/app/src/cli/models/extensions/specifications/admin.ts index bdd20cb2131..515cfa6706c 100644 --- a/packages/app/src/cli/models/extensions/specifications/admin.ts +++ b/packages/app/src/cli/models/extensions/specifications/admin.ts @@ -2,6 +2,8 @@ import {createExtensionSpecification} from '../specification.js' import {BaseConfigType, ZodSchemaType} from '../schemas.js' import {zod} from '@shopify/cli-kit/node/schema' import {joinPath} from '@shopify/cli-kit/node/path' +import {fileExists} from '@shopify/cli-kit/node/fs' +import {AbortError} from '@shopify/cli-kit/node/error' export const AdminSpecIdentifier = 'admin' @@ -31,6 +33,20 @@ const adminSpecificationSpec = createExtensionSpecification({ const path = joinPath(extension.directory, staticRoot, '**/*') return {paths: [path], ignore: []} }, + buildValidation: async (extension) => { + const staticRoot = extension.configuration.admin?.static_root + if (!staticRoot) return + + const indexHtmlPath = joinPath(extension.directory, staticRoot, 'index.html') + const indexExists = await fileExists(indexHtmlPath) + + if (!indexExists) { + throw new AbortError( + `The admin extension requires an index.html file in the static_root directory (${staticRoot}), but it was not found.`, + `This usually means the build step has not completed yet. Make sure your app runs a build command (e.g., via a predev hook in your web configuration) before starting the dev server.`, + ) + } + }, transformRemoteToLocal: (remoteContent) => { return { admin: {