Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add base infrastructure and first command for multi-environment commands #5284

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
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
5 changes: 5 additions & 0 deletions .changeset/chatty-pandas-sort.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/cli': minor
---

Add multi-environment infrastructure and allow multiple environment usage in theme list command
13 changes: 13 additions & 0 deletions packages/cli-kit/src/public/node/base-command.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,19 @@ describe('applying environments', async () => {
expect(outputMock.info()).toEqual('')
})

runTestInTmpDir('does not apply flags when multiple environments are specified', async (tmpDir: string) => {
// Given
const outputMock = mockAndCaptureOutput()
outputMock.clear()

// When
await MockCommand.run(['--path', tmpDir, '--environment', 'validEnvironment', '--environment', 'validEnvironment'])

// Then
expect(testResult).toEqual({})
expect(outputMock.info()).toEqual('')
})

runTestInTmpDir('applies a environment when one is specified', async (tmpDir: string) => {
// Given
const outputMock = mockAndCaptureOutput()
Expand Down
9 changes: 6 additions & 3 deletions packages/cli-kit/src/public/node/base-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {Command, Errors} from '@oclif/core'
import {FlagOutput, Input, ParserOutput, FlagInput, ArgOutput} from '@oclif/core/lib/interfaces/parser.js'

interface EnvironmentFlags {
environment?: string
environment?: string | string[]
path?: string
}

Expand Down Expand Up @@ -47,7 +47,7 @@ abstract class BaseCommand extends Command {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
protected async init(): Promise<any> {
this.exitWithTimestampWhenEnvVariablePresent()
setCurrentCommandId(this.id || '')
setCurrentCommandId(this.id ?? '')
if (!isDevelopment()) {
// This function runs just prior to `run`
await registerCleanBugsnagErrorsFromWithinPlugins(this.config)
Expand Down Expand Up @@ -139,6 +139,9 @@ This flag is required in non-interactive terminal environments, such as a CI env
const environmentsFileName = this.environmentsFilename()
if (!flags.environment || !environmentsFileName) return originalResult

// If users pass multiple environments, do not load them and let each command handle it
if (Array.isArray(flags.environment)) return originalResult

// If the specified environment isn't found, don't modify the results
const environment = await loadEnvironment(flags.environment, environmentsFileName, {from: flags.path})
if (!environment) return originalResult
Expand All @@ -152,7 +155,7 @@ This flag is required in non-interactive terminal environments, such as a CI env
// Replace the original result with this one.
const result = await super.parse<TFlags, TGlobalFlags, TArgs>(options, [
// Need to specify argv default because we're merging with argsFromEnvironment.
...(argv || this.argv),
...(argv ?? this.argv),
...argsFromEnvironment<TFlags, TGlobalFlags, TArgs>(environment, options, noDefaultsResult),
])

Expand Down
246 changes: 123 additions & 123 deletions packages/cli/README.md

Large diffs are not rendered by default.

34 changes: 19 additions & 15 deletions packages/cli/oclif.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -4850,7 +4850,7 @@
"description": "The environment to apply to the current command.",
"env": "SHOPIFY_FLAG_ENVIRONMENT",
"hasDynamicHelp": false,
"multiple": false,
"multiple": true,
"name": "environment",
"type": "option"
},
Expand Down Expand Up @@ -4970,7 +4970,7 @@
"description": "The environment to apply to the current command.",
"env": "SHOPIFY_FLAG_ENVIRONMENT",
"hasDynamicHelp": false,
"multiple": false,
"multiple": true,
"name": "environment",
"type": "option"
},
Expand Down Expand Up @@ -5061,7 +5061,7 @@
"description": "The environment to apply to the current command.",
"env": "SHOPIFY_FLAG_ENVIRONMENT",
"hasDynamicHelp": false,
"multiple": false,
"multiple": true,
"name": "environment",
"type": "option"
},
Expand Down Expand Up @@ -5148,7 +5148,7 @@
"description": "The environment to apply to the current command.",
"env": "SHOPIFY_FLAG_ENVIRONMENT",
"hasDynamicHelp": false,
"multiple": false,
"multiple": true,
"name": "environment",
"type": "option"
},
Expand Down Expand Up @@ -5338,7 +5338,7 @@
"description": "The environment to apply to the current command.",
"env": "SHOPIFY_FLAG_ENVIRONMENT",
"hasDynamicHelp": false,
"multiple": false,
"multiple": true,
"name": "environment",
"type": "option"
},
Expand Down Expand Up @@ -5521,7 +5521,7 @@
"description": "The environment to apply to the current command.",
"env": "SHOPIFY_FLAG_ENVIRONMENT",
"hasDynamicHelp": false,
"multiple": false,
"multiple": true,
"name": "environment",
"type": "option"
},
Expand Down Expand Up @@ -5601,6 +5601,10 @@
"hiddenAliases": [
],
"id": "theme:list",
"multiEnvironmentsFlags": [
"store",
"password"
],
"pluginAlias": "@shopify/cli",
"pluginName": "@shopify/cli",
"pluginType": "core",
Expand All @@ -5620,7 +5624,7 @@
"description": "The environment to apply to the current command.",
"env": "SHOPIFY_FLAG_ENVIRONMENT",
"hasDynamicHelp": false,
"multiple": false,
"multiple": true,
"name": "environment",
"type": "option"
},
Expand Down Expand Up @@ -5716,7 +5720,7 @@
"description": "The environment to apply to the current command.",
"env": "SHOPIFY_FLAG_ENVIRONMENT",
"hasDynamicHelp": false,
"multiple": false,
"multiple": true,
"name": "environment",
"type": "option"
},
Expand Down Expand Up @@ -5840,7 +5844,7 @@
"description": "The environment to apply to the current command.",
"env": "SHOPIFY_FLAG_ENVIRONMENT",
"hasDynamicHelp": false,
"multiple": false,
"multiple": true,
"name": "environment",
"type": "option"
},
Expand Down Expand Up @@ -5941,7 +5945,7 @@
"description": "The environment to apply to the current command.",
"env": "SHOPIFY_FLAG_ENVIRONMENT",
"hasDynamicHelp": false,
"multiple": false,
"multiple": true,
"name": "environment",
"type": "option"
},
Expand Down Expand Up @@ -6028,7 +6032,7 @@
"description": "The environment to apply to the current command.",
"env": "SHOPIFY_FLAG_ENVIRONMENT",
"hasDynamicHelp": false,
"multiple": false,
"multiple": true,
"name": "environment",
"type": "option"
},
Expand Down Expand Up @@ -6167,7 +6171,7 @@
"description": "The environment to apply to the current command.",
"env": "SHOPIFY_FLAG_ENVIRONMENT",
"hasDynamicHelp": false,
"multiple": false,
"multiple": true,
"name": "environment",
"type": "option"
},
Expand Down Expand Up @@ -6334,7 +6338,7 @@
"description": "The environment to apply to the current command.",
"env": "SHOPIFY_FLAG_ENVIRONMENT",
"hasDynamicHelp": false,
"multiple": false,
"multiple": true,
"name": "environment",
"type": "option"
},
Expand Down Expand Up @@ -6423,7 +6427,7 @@
"description": "The environment to apply to the current command.",
"env": "SHOPIFY_FLAG_ENVIRONMENT",
"hasDynamicHelp": false,
"multiple": false,
"multiple": true,
"name": "environment",
"type": "option"
},
Expand Down Expand Up @@ -6609,7 +6613,7 @@
"description": "The environment to apply to the current command.",
"env": "SHOPIFY_FLAG_ENVIRONMENT",
"hasDynamicHelp": false,
"multiple": false,
"multiple": true,
"name": "environment",
"type": "option"
},
Expand Down
16 changes: 8 additions & 8 deletions packages/theme/src/cli/commands/theme/list.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import {ensureThemeStore} from '../../utilities/theme-store.js'
import {list} from '../../services/list.js'
import {ALLOWED_ROLES, Role} from '../../utilities/theme-selector/fetch.js'
import {themeFlags} from '../../flags.js'
import ThemeCommand from '../../utilities/theme-command.js'
import {list} from '../../services/list.js'
import {Flags} from '@oclif/core'
import {ensureAuthenticatedThemes} from '@shopify/cli-kit/node/session'
import {globalFlags, jsonFlag} from '@shopify/cli-kit/node/cli'
import {OutputFlags} from '@oclif/core/lib/interfaces/parser.js'
import {AdminSession} from '@shopify/cli-kit/node/session'

type ListFlags = OutputFlags<typeof List.flags>

export default class List extends ThemeCommand {
static description = 'Lists the themes in your store, along with their IDs and statuses.'
Expand All @@ -31,11 +33,9 @@ export default class List extends ThemeCommand {
environment: themeFlags.environment,
}

async run(): Promise<void> {
const {flags} = await this.parse(List)
const store = ensureThemeStore(flags)
const adminSession = await ensureAuthenticatedThemes(store, flags.password)
static multiEnvironmentsFlags = ['store', 'password']

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These flags are the necessary flags the toml file would need to run. For instance, in a push or pull theme, you would be adding the theme flag to this array.

await list(adminSession, flags)
async command(flags: ListFlags, adminSession: AdminSession) {
await list(flags, adminSession)
}
}
1 change: 1 addition & 0 deletions packages/theme/src/cli/flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,6 @@ export const themeFlags = {
char: 'e',
description: 'The environment to apply to the current command.',
env: 'SHOPIFY_FLAG_ENVIRONMENT',
multiple: true,
}),
}
2 changes: 1 addition & 1 deletion packages/theme/src/cli/services/info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
interface ThemeInfoOptions {
store?: string
password?: string
environment?: string
environment?: string[]
development?: boolean
theme?: string
json?: boolean
Expand Down Expand Up @@ -60,7 +60,7 @@
}

function devConfigSection(): AlertCustomSection {
const store = getThemeStore() || 'Not configured'

Check warning on line 63 in packages/theme/src/cli/services/info.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/theme/src/cli/services/info.ts#L63

[@typescript-eslint/prefer-nullish-coalescing] Prefer using nullish coalescing operator (`??`) instead of a logical or (`||`), as it is a safer operator.
const developmentTheme = getDevelopmentTheme()
return tabularSection('Theme Configuration', [
['Store', store],
Expand All @@ -73,7 +73,7 @@
return tabularSection('Tooling and System', [
['Shopify CLI', config.cliVersion],
['OS', `${platform}-${arch}`],
['Shell', process.env.SHELL || 'unknown'],

Check warning on line 76 in packages/theme/src/cli/services/info.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/theme/src/cli/services/info.ts#L76

[@typescript-eslint/prefer-nullish-coalescing] Prefer using nullish coalescing operator (`??`) instead of a logical or (`||`), as it is a safer operator.
['Node version', process.version],
])
}
Expand Down
6 changes: 3 additions & 3 deletions packages/theme/src/cli/services/list.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ describe('list', () => {
vi.mocked(getDevelopmentTheme).mockReturnValue(developmentThemeId.toString())
vi.mocked(getHostTheme).mockReturnValue(hostThemeId.toString())

await list(session, {json: false})
await list({json: false}, session)

expect(renderTable).toBeCalledWith({
rows: [
Expand All @@ -54,7 +54,7 @@ describe('list', () => {
{id: 5, name: 'Theme 5', role: 'development'},
] as Theme[])

await list(session, {role: 'live', name: '*eMe 3*', json: false})
await list({role: 'live', name: '*eMe 3*', json: false}, session)

expect(renderTable).toBeCalledWith({
rows: [{id: '#3', name: 'Theme 3', role: '[live]'}],
Expand All @@ -70,7 +70,7 @@ describe('list', () => {
{id: 2, name: 'Theme 2', role: ''},
] as Theme[])

await list(session, {json: true})
await list({json: true}, session)

expect(mockOutput.info()).toMatchInlineSnapshot(`
"[
Expand Down
42 changes: 33 additions & 9 deletions packages/theme/src/cli/services/list.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import {columns} from './list.columns.js'
import {getDevelopmentTheme} from './local-storage.js'
import {ALLOWED_ROLES, fetchStoreThemes, Role} from '../utilities/theme-selector/fetch.js'
import {Filter, FilterProps, filterThemes} from '../utilities/theme-selector/filter.js'
import {renderTable} from '@shopify/cli-kit/node/ui'
import {InlineToken, renderInfo} from '@shopify/cli-kit/node/ui'
import {AdminSession} from '@shopify/cli-kit/node/session'
import {getHostTheme} from '@shopify/cli-kit/node/themes/conf'
import {outputInfo} from '@shopify/cli-kit/node/output'
Expand All @@ -12,9 +11,20 @@ interface Options {
name?: string
id?: number
json: boolean
environment?: string
}

export async function list(adminSession: AdminSession, options: Options) {
function tabularSection(
title: string,
data: InlineToken[][],
): {title: string; body: {tabularData: InlineToken[][]; firstColumnSubdued?: boolean}} {
return {
title,
body: {tabularData: data},
}
}

export async function list(options: Options, adminSession: AdminSession) {
const store = adminSession.storeFqdn
const filter = new Filter({
...ALLOWED_ROLES.reduce((roles: FilterProps, role) => {
Expand Down Expand Up @@ -43,12 +53,26 @@ export async function list(adminSession: AdminSession, options: Options) {
formattedRole += ' [yours]'
}
}
return {
id: `#${id}`,
name,
role: formattedRole,
}
return [name, formattedRole, `#${id}`]
})

renderTable({rows: themes, columns})
const tableData = [
['name', 'role', 'id'],
['────────────────────────────', '──────────────────────', '──────────────'],
...themes,
]

renderInfo({
customSections: [
...(options.environment
? [
{
title: 'Environment',
body: [{subdued: `Environment name: ${options.environment}\nStore: ${store}`}],
},
]
: []),
tabularSection('', tableData),
],
})
}
Loading
Loading