Skip to content

Commit

Permalink
Add workflow for discount function settings
Browse files Browse the repository at this point in the history
  • Loading branch information
devisscher committed Dec 12, 2024
1 parent 7192936 commit 87524f5
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 53 deletions.
20 changes: 10 additions & 10 deletions packages/app/src/cli/prompts/generate/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,12 @@ export function buildChoices(extensionTemplates: ExtensionTemplate[], unavailabl
return templateSpecChoices.sort(compareChoices)
}

const generateExtensionPrompts = async (
export const generateExtensionPrompts = async (

Check failure on line 76 in packages/app/src/cli/prompts/generate/extension.ts

View workflow job for this annotation

GitHub Actions / knip-reporter-annotations-check

packages/app/src/cli/prompts/generate/extension.ts#L76

'generateExtensionPrompts' is a duplicate of 'default'
options: GenerateExtensionPromptOptions,
): Promise<GenerateExtensionPromptOutput> => {
let extensionTemplates = options.extensionTemplates
let templateType = options.templateType
const extensionFlavor = options.extensionFlavor
console.log('EXTENSION TEMPLATES', extensionTemplates)
if (!templateType) {
if (extensionFlavor) {
extensionTemplates = extensionTemplates.filter((template) =>
Expand All @@ -91,11 +90,15 @@ const generateExtensionPrompts = async (
throw new AbortError('You have reached the limit for the number of extensions you can create.')
}

// eslint-disable-next-line require-atomic-updates
templateType = await renderAutocompletePrompt({
message: 'Type of extension?',
choices: buildChoices(extensionTemplates, options.unavailableExtensions),
})
if (extensionTemplates.length === 1) {
templateType = extensionTemplates[0]?.identifier
} else {
// eslint-disable-next-line require-atomic-updates
templateType = await renderAutocompletePrompt({
message: 'Type of extension?',
choices: buildChoices(extensionTemplates, options.unavailableExtensions),
})
}
}

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
Expand All @@ -106,10 +109,7 @@ const generateExtensionPrompts = async (
const extensionContent = {
name,
flavor,
// extensionChildren [identifiers]
extensionChildren: extensionTemplate.relatedExtensions?.map((elem) => elem.name) ?? [],
}
// TODO: here perhaps return a flag, that lets us know that there are possible next steps

return {extensionTemplate, extensionContent}
}
Expand Down
46 changes: 6 additions & 40 deletions packages/app/src/cli/services/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {AppInterface, AppLinkedInterface} from '../models/app/app.js'
import generateExtensionPrompts, {
GenerateExtensionPromptOptions,
GenerateExtensionPromptOutput,
promptAddExtensionConfirmation,
} from '../prompts/generate/extension.js'
import metadata from '../metadata.js'
import {
Expand Down Expand Up @@ -41,63 +40,32 @@ export interface GenerateOptions {
async function generate(options: GenerateOptions) {
const {app, developerPlatformClient, remoteApp, specifications, template} = options

// console.log('GENERATE', options)

const availableSpecifications = specifications.map((spec) => spec.identifier)
const extensionTemplates = await fetchExtensionTemplates(developerPlatformClient, remoteApp, availableSpecifications)

const promptOptions = await buildPromptOptions(extensionTemplates, specifications, app, options)
const promptAnswers = await generateExtensionPrompts(promptOptions)

// Add related extension children for product discounts
if (promptAnswers.extensionTemplate.identifier === 'product_discounts') {
const settingsTemplate = extensionTemplates.find((template) => template.identifier === 'discount_function_settings')
if (settingsTemplate) {
const mockSettingsPromptAnswers: GenerateExtensionPromptOutput = {
extensionTemplate: settingsTemplate,
extensionContent: {
name: `${promptAnswers.extensionContent.name}-settings`,
flavor: 'react',
relatedExtensions: [],
},
}

// Generate the settings extension
const settingsGenerateOptions = buildGenerateOptions(
mockSettingsPromptAnswers,
app,
options,
developerPlatformClient,
)
await generateExtensionTemplate(settingsGenerateOptions)
}
}

console.log('PROMPT ANSWERS', promptAnswers)
// Call module related to that child extension
await saveAnalyticsMetadata(promptAnswers, template)

// We can add an extra generated extension here.
const generateExtensionOptions = buildGenerateOptions(promptAnswers, app, options, developerPlatformClient)
const generatedExtension = await generateExtensionTemplate(generateExtensionOptions)

renderSuccessMessage(generatedExtension, app.packageManager)
const workflow = workflowRegistry[generatedExtension.extensionTemplate.identifier]
await workflow?.afterGenerate({
generateOptions: options,
extensionTemplateOptions: generateExtensionOptions,
generatedExtension,
extensionTemplateOptions: generateExtensionOptions,
extensionTemplates: extensionTemplates,

Check failure on line 59 in packages/app/src/cli/services/generate.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/app/src/cli/services/generate.ts#L59

[object-shorthand] Expected property shorthand.
})

renderSuccessMessage(generatedExtension, app.packageManager)
}

async function buildPromptOptions(
export async function buildPromptOptions(
extensionTemplates: ExtensionTemplate[],
specifications: ExtensionSpecification[],
app: AppInterface,
options: GenerateOptions,
): Promise<GenerateExtensionPromptOptions> {
// console.log('BUILDING PROMPT OPTIONS', arguments)
const extensionTemplate = await handleTypeParameter(options.template, app, extensionTemplates, specifications)
validateExtensionFlavor(extensionTemplate, options.flavor)

Expand Down Expand Up @@ -129,7 +97,6 @@ function checkLimits(
}

function limitReached(app: AppInterface, specifications: ExtensionSpecification[], template: ExtensionTemplate) {
// console.log('CHECKING LIMITS', specifications)
const type = template.type
const specification = specifications.find((spec) => spec.identifier === type || spec.externalIdentifier === type)
const existingExtensions = app.extensionsForType({identifier: type, externalIdentifier: type})
Expand All @@ -138,15 +105,14 @@ function limitReached(app: AppInterface, specifications: ExtensionSpecification[

async function saveAnalyticsMetadata(promptAnswers: GenerateExtensionPromptOutput, typeFlag: string | undefined) {
const {extensionContent} = promptAnswers
console.log('SAVING METADATA', promptAnswers)
return metadata.addPublicMetadata(() => ({
cmd_scaffold_template_flavor: extensionContent.flavor,
cmd_scaffold_type: promptAnswers.extensionTemplate.identifier,
cmd_scaffold_used_prompts_for_type: !typeFlag,
}))
}

function buildGenerateOptions(
export function buildGenerateOptions(
promptAnswers: GenerateExtensionPromptOutput,
app: AppInterface,
options: GenerateOptions,
Expand All @@ -161,7 +127,7 @@ function buildGenerateOptions(
}
}

function renderSuccessMessage(extension: GeneratedExtension, packageManager: AppInterface['packageManager']) {
export function renderSuccessMessage(extension: GeneratedExtension, packageManager: AppInterface['packageManager']) {
const formattedSuccessfulMessage = formatSuccessfulRunMessage(
extension.extensionTemplate,
extension.directory,
Expand Down
10 changes: 7 additions & 3 deletions packages/app/src/cli/services/generate/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ function getTemplateLanguage(flavor: ExtensionFlavorValue | undefined): Template
export interface GeneratedExtension {
directory: string
extensionTemplate: ExtensionTemplate
handle: string
}

interface ExtensionInitOptions {
Expand All @@ -81,7 +82,6 @@ export async function generateExtensionTemplate(
const extensionFlavor = options.extensionTemplate.supportedFlavors.find(
(flavor) => flavor.value === extensionFlavorValue,
)
// TODO For grouping we might want to setup directories/groups for extensions
const directory = await ensureExtensionDirectoryExists({app: options.app, name: extensionName})
const url = options.cloneUrl || options.extensionTemplate.url

Check warning on line 86 in packages/app/src/cli/services/generate/extension.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/app/src/cli/services/generate/extension.ts#L86

[@typescript-eslint/prefer-nullish-coalescing] Prefer using nullish coalescing operator (`??`) instead of a logical or (`||`), as it is a safer operator.
const uid = options.developerPlatformClient.supportsAtomicDeployments ? randomUUID() : undefined
Expand All @@ -100,7 +100,11 @@ export async function generateExtensionTemplate(
}),
}
await extensionInit(initOptions)
return {directory: relativizePath(directory), extensionTemplate: options.extensionTemplate}
return {
directory: relativizePath(directory),
extensionTemplate: options.extensionTemplate,
handle: slugify(extensionName),
}
}

async function extensionInit(options: ExtensionInitOptions) {
Expand Down Expand Up @@ -225,7 +229,7 @@ async function uiExtensionInit({
onGetTemplateRepository,
}: ExtensionInitOptions) {
const templateLanguage = getTemplateLanguage(extensionFlavor?.value)

console.log('extensionName', name)

Check failure on line 232 in packages/app/src/cli/services/generate/extension.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/app/src/cli/services/generate/extension.ts#L232

[no-console] Unexpected console statement.
const tasks = [
{
title: `Generating extension`,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {renderConfirmationPrompt} from '@shopify/cli-kit/node/ui'

Check failure on line 1 in packages/app/src/cli/services/generate/workflows/discount-details-function-settings-collection.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/app/src/cli/services/generate/workflows/discount-details-function-settings-collection.ts#L1

[import/order] `@shopify/cli-kit/node/ui` import should occur after import of `../../../prompts/generate/extension.js`
import {Workflow} from './registry.js'
import {generateExtensionTemplate} from '../extension.js'
import {generateExtensionPrompts} from '../../../prompts/generate/extension.js'

Check failure on line 4 in packages/app/src/cli/services/generate/workflows/discount-details-function-settings-collection.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/app/src/cli/services/generate/workflows/discount-details-function-settings-collection.ts#L4

[import/no-duplicates] '../../../prompts/generate/extension.js' imported multiple times.
import {buildGenerateOptions, renderSuccessMessage, buildPromptOptions} from '../../generate.js'
import {GenerateExtensionPromptOutput} from '../../../prompts/generate/extension.js'

Check failure on line 6 in packages/app/src/cli/services/generate/workflows/discount-details-function-settings-collection.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/app/src/cli/services/generate/workflows/discount-details-function-settings-collection.ts#L6

[unused-imports/no-unused-imports] 'GenerateExtensionPromptOutput' is defined but never used.

Check failure on line 6 in packages/app/src/cli/services/generate/workflows/discount-details-function-settings-collection.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/app/src/cli/services/generate/workflows/discount-details-function-settings-collection.ts#L6

[@typescript-eslint/no-unused-vars] 'GenerateExtensionPromptOutput' is defined but never used. Allowed unused vars must match /^_/u.

Check failure on line 6 in packages/app/src/cli/services/generate/workflows/discount-details-function-settings-collection.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/app/src/cli/services/generate/workflows/discount-details-function-settings-collection.ts#L6

[import/no-duplicates] '../../../prompts/generate/extension.js' imported multiple times.

export const discountDetailsFunctionSettingsCollection: Workflow = {
afterGenerate: async (options) => {
const {app, developerPlatformClient, specifications} = options.generateOptions

const shouldCreateFunction = await renderConfirmationPrompt({
message: 'Would you like to create a function for this extension?',
defaultValue: true,
})

if (shouldCreateFunction) {
// create a function extension
const extensionTemplates = options.extensionTemplates.filter(
(template) =>
template.identifier === 'shipping_discounts' ||
template.identifier === 'product_discounts' ||
template.identifier === 'order_discounts' ||
template.identifier == 'discounts_allocator',

Check failure on line 24 in packages/app/src/cli/services/generate/workflows/discount-details-function-settings-collection.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/app/src/cli/services/generate/workflows/discount-details-function-settings-collection.ts#L24

[eqeqeq] Expected '===' and instead saw '=='.
)

const promptOptions = await buildPromptOptions(extensionTemplates, specifications, app, options.generateOptions)
const promptAnswers = await generateExtensionPrompts(promptOptions)

const generateExtensionOptions = buildGenerateOptions(
promptAnswers,
app,
options.generateOptions,
developerPlatformClient,
)
const generatedExtension = await generateExtensionTemplate(generateExtensionOptions)
renderSuccessMessage(generatedExtension, app.packageManager)
}
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {deepMergeObjects} from '@shopify/cli-kit/common/object'
import {readFile, writeFile} from '@shopify/cli-kit/node/fs'
import {zod} from '@shopify/cli-kit/node/schema'
import {decodeToml, encodeToml} from '@shopify/cli-kit/node/toml'

export interface PatchTomlOptions {

Check failure on line 6 in packages/app/src/cli/services/generate/workflows/patch-configuration-file.ts

View workflow job for this annotation

GitHub Actions / knip-reporter-annotations-check

packages/app/src/cli/services/generate/workflows/patch-configuration-file.ts#L6

'PatchTomlOptions' is an unused type
path: string
patch: {[key: string]: unknown}
schema?: zod.AnyZodObject
}

function mergeArrayStrategy(existingArray: unknown[], newArray: unknown[]): unknown[] {
if (
existingArray.length > 0 &&
existingArray[0] &&
typeof existingArray[0] === 'object' &&
newArray[0] &&
typeof newArray[0] === 'object'
) {
return [{...(existingArray[0] as object), ...(newArray[0] as object)}]

Check failure on line 20 in packages/app/src/cli/services/generate/workflows/patch-configuration-file.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/app/src/cli/services/generate/workflows/patch-configuration-file.ts#L20

[@typescript-eslint/no-unnecessary-type-assertion] This assertion is unnecessary since it does not change the type of the expression.

Check failure on line 20 in packages/app/src/cli/services/generate/workflows/patch-configuration-file.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/app/src/cli/services/generate/workflows/patch-configuration-file.ts#L20

[@typescript-eslint/no-unnecessary-type-assertion] This assertion is unnecessary since it does not change the type of the expression.
}
return newArray
}

export async function patchAppConfigurationFile({path, patch}: PatchTomlOptions) {
const tomlContents = await readFile(path)
const configuration = decodeToml(tomlContents)

// Deep merge with custom array strategy
const updatedConfig = deepMergeObjects(configuration, patch, mergeArrayStrategy)

const encodedString = encodeToml(updatedConfig)
await writeFile(path, encodedString)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {renderConfirmationPrompt} from '@shopify/cli-kit/node/ui'

Check failure on line 1 in packages/app/src/cli/services/generate/workflows/product-discount-function-collection.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/app/src/cli/services/generate/workflows/product-discount-function-collection.ts#L1

[import/order] `@shopify/cli-kit/node/ui` import should occur after import of `./patch-configuration-file.js`
import {Workflow} from './registry.js'
import {generateExtensionTemplate} from '../extension.js'

Check failure on line 3 in packages/app/src/cli/services/generate/workflows/product-discount-function-collection.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/app/src/cli/services/generate/workflows/product-discount-function-collection.ts#L3

[import/order] `../extension.js` import should occur after import of `./patch-configuration-file.js`
import {generateExtensionPrompts} from '../../../prompts/generate/extension.js'

Check failure on line 4 in packages/app/src/cli/services/generate/workflows/product-discount-function-collection.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/app/src/cli/services/generate/workflows/product-discount-function-collection.ts#L4

[import/order] `../../../prompts/generate/extension.js` import should occur after import of `./patch-configuration-file.js`

Check failure on line 4 in packages/app/src/cli/services/generate/workflows/product-discount-function-collection.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/app/src/cli/services/generate/workflows/product-discount-function-collection.ts#L4

[import/no-duplicates] '../../../prompts/generate/extension.js' imported multiple times.
import {buildGenerateOptions, renderSuccessMessage, buildPromptOptions} from '../../generate.js'

Check failure on line 5 in packages/app/src/cli/services/generate/workflows/product-discount-function-collection.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/app/src/cli/services/generate/workflows/product-discount-function-collection.ts#L5

[import/order] `../../generate.js` import should occur after import of `./patch-configuration-file.js`
import {GenerateExtensionPromptOutput} from '../../../prompts/generate/extension.js'

Check failure on line 6 in packages/app/src/cli/services/generate/workflows/product-discount-function-collection.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/app/src/cli/services/generate/workflows/product-discount-function-collection.ts#L6

[import/order] `../../../prompts/generate/extension.js` import should occur after import of `./patch-configuration-file.js`

Check failure on line 6 in packages/app/src/cli/services/generate/workflows/product-discount-function-collection.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/app/src/cli/services/generate/workflows/product-discount-function-collection.ts#L6

[unused-imports/no-unused-imports] 'GenerateExtensionPromptOutput' is defined but never used.

Check failure on line 6 in packages/app/src/cli/services/generate/workflows/product-discount-function-collection.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/app/src/cli/services/generate/workflows/product-discount-function-collection.ts#L6

[@typescript-eslint/no-unused-vars] 'GenerateExtensionPromptOutput' is defined but never used. Allowed unused vars must match /^_/u.

Check failure on line 6 in packages/app/src/cli/services/generate/workflows/product-discount-function-collection.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/app/src/cli/services/generate/workflows/product-discount-function-collection.ts#L6

[import/no-duplicates] '../../../prompts/generate/extension.js' imported multiple times.
import {patchAppConfigurationFile} from './patch-configuration-file.js'

export const productDiscountFunctionCollection: Workflow = {
afterGenerate: async (options) => {
const {app, developerPlatformClient, specifications} = options.generateOptions
const functionTomlFilePath = `${options.generatedExtension.directory}/shopify.extension.toml`

const shouldLinkExtension = await renderConfirmationPrompt({
message: 'Would you like to create a UI extension for your function?',
defaultValue: true,
})

if (shouldLinkExtension) {
// create a UI extension
const extensionTemplates = options.extensionTemplates.filter(
(template) => template.identifier === 'discount_details_function_settings',
)

const promptOptions = await buildPromptOptions(extensionTemplates, specifications, app, options.generateOptions)
const promptAnswers = await generateExtensionPrompts(promptOptions)

const generateExtensionOptions = buildGenerateOptions(
promptAnswers,
app,
options.generateOptions,
developerPlatformClient,
)
const generatedExtension = await generateExtensionTemplate(generateExtensionOptions)

const patch = {
extensions: [
{
ui: {
handle: generatedExtension.handle,
},
},
],
}

await patchAppConfigurationFile({
path: functionTomlFilePath,
patch,
})
renderSuccessMessage(generatedExtension, app.packageManager)
}
},
}
6 changes: 6 additions & 0 deletions packages/app/src/cli/services/generate/workflows/registry.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import {editorExtensionCollection} from './editor-extension-collection.js'
import {GenerateOptions} from '../../generate.js'
import {GeneratedExtension, GenerateExtensionTemplateOptions} from '../../generate/extension.js'
import {discountDetailsFunctionSettingsCollection} from './discount-details-function-settings-collection.js'

Check failure on line 4 in packages/app/src/cli/services/generate/workflows/registry.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/app/src/cli/services/generate/workflows/registry.ts#L4

[import/order] `./discount-details-function-settings-collection.js` import should occur before import of `../../generate.js`
import {productDiscountFunctionCollection} from './product-discount-function-collection.js'

Check failure on line 5 in packages/app/src/cli/services/generate/workflows/registry.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/app/src/cli/services/generate/workflows/registry.ts#L5

[import/order] `./product-discount-function-collection.js` import should occur before import of `../../generate.js`
import {ExtensionTemplate} from '../../../models/app/template.js'

interface AfterGenerateOptions {
generateOptions: GenerateOptions
extensionTemplateOptions: GenerateExtensionTemplateOptions
extensionTemplates: ExtensionTemplate[]
generatedExtension: GeneratedExtension
}

Expand All @@ -18,4 +22,6 @@ interface WorkflowRegistry {

export const workflowRegistry: WorkflowRegistry = {
editor_extension_collection: editorExtensionCollection,
discount_details_function_settings: discountDetailsFunctionSettingsCollection,
product_discounts: productDiscountFunctionCollection,
}

0 comments on commit 87524f5

Please sign in to comment.