Skip to content
Draft
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
5 changes: 5 additions & 0 deletions .changeset/fix-template-json-path-1p-dev.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/app': patch
---

Fix `SHOPIFY_CLI_APP_TEMPLATES_JSON_PATH` env var not working when `SHOPIFY_CLI_1P_DEV` is enabled. The template override now works consistently regardless of which developer platform client is selected.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {PartnersClient} from './partners-client.js'
import {CreateAppQuery} from '../../api/graphql/create_app.js'
import {RemoteTemplateSpecificationsQuery} from '../../api/graphql/template_specifications.js'
import {AppInterface, WebType} from '../../models/app/app.js'
import {Organization, OrganizationSource, OrganizationStore} from '../../models/organization.js'
import {
Expand All @@ -11,7 +12,9 @@ import {
import {appNamePrompt} from '../../prompts/dev.js'
import {FindOrganizationQuery} from '../../api/graphql/find_org.js'
import {partnersRequest} from '@shopify/cli-kit/node/api/partners'
import {describe, expect, vi, test, beforeEach} from 'vitest'
import {inTemporaryDirectory, writeFile} from '@shopify/cli-kit/node/fs'
import {joinPath} from '@shopify/cli-kit/node/path'
import {describe, expect, vi, test, beforeEach, afterEach} from 'vitest'

vi.mock('../../prompts/dev.js')
vi.mock('@shopify/cli-kit/node/api/partners')
Expand Down Expand Up @@ -230,3 +233,97 @@ describe('singleton pattern', () => {
expect(instance1).not.toBe(instance2)
})
})

describe('templateSpecifications', () => {
const originalEnv = process.env

afterEach(() => {
process.env = originalEnv
})

test('fetches templates from GraphQL when no override is set', async () => {
// Given
const partnersClient = PartnersClient.getInstance(testPartnersUserSession)
const mockTemplates = {
templateSpecifications: [
{
identifier: 'test-template',
name: 'Test Template',
defaultName: 'test',
group: 'TestGroup',
sortPriority: 1,
supportLinks: [],
types: [{url: 'https://example.com', type: 'test', extensionPoints: [], supportedFlavors: []}],
},
],
}
vi.mocked(partnersRequest).mockResolvedValueOnce(mockTemplates)

// When
const result = await partnersClient.templateSpecifications({apiKey: 'test-api-key'})

// Then
expect(partnersRequest).toHaveBeenCalledWith(
RemoteTemplateSpecificationsQuery,
'token',
{apiKey: 'test-api-key'},
undefined,
undefined,
{type: 'token_refresh', handler: expect.any(Function)},
)
expect(result.templates).toHaveLength(1)
expect(result.templates[0]!.identifier).toBe('test-template')
})

test('loads templates from JSON file when SHOPIFY_CLI_APP_TEMPLATES_JSON_PATH is set', async () => {
await inTemporaryDirectory(async (tmpDir) => {
// Given
const templatesPath = joinPath(tmpDir, 'templates.json')
const templates = [
{
identifier: 'json-template',
name: 'JSON Template',
defaultName: 'json-test',
group: 'JsonGroup',
sortPriority: 1,
supportLinks: [],
type: 'json-type',
url: 'https://example.com/json',
extensionPoints: [],
supportedFlavors: [],
},
]
await writeFile(templatesPath, JSON.stringify(templates))
process.env = {...originalEnv, SHOPIFY_CLI_APP_TEMPLATES_JSON_PATH: templatesPath}

const partnersClient = PartnersClient.getInstance(testPartnersUserSession)

// When
const result = await partnersClient.templateSpecifications({apiKey: 'test-api-key'})

// Then
expect(partnersRequest).not.toHaveBeenCalledWith(
RemoteTemplateSpecificationsQuery,
expect.anything(),
expect.anything(),
expect.anything(),
expect.anything(),
expect.anything(),
)
expect(result.templates).toHaveLength(1)
expect(result.templates[0]!.identifier).toBe('json-template')
expect(result.templates[0]!.type).toBe('json-type')
})
})

test('throws error when SHOPIFY_CLI_APP_TEMPLATES_JSON_PATH points to non-existent file', async () => {
// Given
process.env = {...originalEnv, SHOPIFY_CLI_APP_TEMPLATES_JSON_PATH: '/non/existent/path.json'}
const partnersClient = PartnersClient.getInstance(testPartnersUserSession)

// When/Then
await expect(partnersClient.templateSpecifications({apiKey: 'test-api-key'})).rejects.toThrow(
'There is no file at the path specified for template specifications',
)
})
})
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import {environmentVariableNames} from '../../constants.js'
import {CreateAppQuery, CreateAppQuerySchema, CreateAppQueryVariables} from '../../api/graphql/create_app.js'
import {
AppVersion,
Expand Down Expand Up @@ -157,6 +158,7 @@ import {AppLogsSubscribeMutationVariables} from '../../api/graphql/app-managemen
import {TypedDocumentNode} from '@graphql-typed-document-node/core'
import {isUnitTest} from '@shopify/cli-kit/node/context/local'
import {AbortError} from '@shopify/cli-kit/node/error'
import {fileExists, readFile} from '@shopify/cli-kit/node/fs'
import {generateFetchAppLogUrl, partnersRequest, partnersRequestDoc} from '@shopify/cli-kit/node/api/partners'
import {CacheOptions, GraphQLVariables, UnauthorizedHandler} from '@shopify/cli-kit/node/api/graphql'
import {ensureAuthenticatedPartners, Session} from '@shopify/cli-kit/node/session'
Expand Down Expand Up @@ -367,18 +369,35 @@ export class PartnersClient implements DeveloperPlatformClient {
}

async templateSpecifications({apiKey}: MinimalAppIdentifiers): Promise<ExtensionTemplatesResult> {
const variables: RemoteTemplateSpecificationsVariables = {apiKey}
const result: RemoteTemplateSpecificationsSchema = await this.request(RemoteTemplateSpecificationsQuery, variables)
const templates = result.templateSpecifications.map((template) => {
const {types, ...rest} = template
return {
...rest,
...types[0],
const {templatesJsonPath} = environmentVariableNames
const overrideFile = process.env[templatesJsonPath]

let templates
if (overrideFile) {
if (!(await fileExists(overrideFile))) {
throw new AbortError('There is no file at the path specified for template specifications')
}
})
const templatesJson = await readFile(overrideFile)
// JSON file is already in flattened ExtensionTemplate format
templates = JSON.parse(templatesJson)
} else {
const variables: RemoteTemplateSpecificationsVariables = {apiKey}
const result: RemoteTemplateSpecificationsSchema = await this.request(
RemoteTemplateSpecificationsQuery,
variables,
)
// GraphQL result needs transformation to flatten the types array
templates = result.templateSpecifications.map((template) => {
const {types, ...rest} = template
return {
...rest,
...types[0],
}
})
}

let counter = 0
const templatesWithPriority = templates.map((template) => ({
const templatesWithPriority = templates.map((template: {sortPriority?: number; group?: string}) => ({
...template,
sortPriority: template.sortPriority ?? counter++,
}))
Expand Down
Loading