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

Revert "[Themes] Remove Ruby from theme push command" #4676

Merged
merged 1 commit into from
Oct 17, 2024
Merged
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
22 changes: 22 additions & 0 deletions packages/cli/oclif.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5760,6 +5760,20 @@
],
"args": {
},
"cli2Flags": [
"theme",
"development",
"live",
"unpublished",
"nodelete",
"only",
"ignore",
"json",
"allow-live",
"publish",
"force",
"development-theme-id"
],
"customPluginName": "@shopify/theme",
"description": "Uploads your local theme files to Shopify, overwriting the remote version if specified.\n\n If no theme is specified, then you're prompted to select the theme to overwrite from the list of the themes in your store.\n\n You can run this command only in a directory that matches the \"default Shopify theme folder structure\" (https://shopify.dev/docs/themes/tools/cli#directory-structure).\n\n This command returns the following information:\n\n - A link to the \"editor\" (https://shopify.dev/docs/themes/tools/online-editor) for the theme in the Shopify admin.\n - A \"preview link\" (https://help.shopify.com/manual/online-store/themes/adding-themes#share-a-theme-preview-with-others) that you can share with others.\n\n If you use the `--json` flag, then theme information is returned in JSON format, which can be used as a machine-readable input for scripts or continuous integration.\n\n Sample output:\n\n ```json\n {\n \"theme\": {\n \"id\": 108267175958,\n \"name\": \"MyTheme\",\n \"role\": \"unpublished\",\n \"shop\": \"mystore.myshopify.com\",\n \"editor_url\": \"https://mystore.myshopify.com/admin/themes/108267175958/editor\",\n \"preview_url\": \"https://mystore.myshopify.com/?preview_theme_id=108267175958\"\n }\n }\n ```\n ",
"descriptionWithMarkdown": "Uploads your local theme files to Shopify, overwriting the remote version if specified.\n\n If no theme is specified, then you're prompted to select the theme to overwrite from the list of the themes in your store.\n\n You can run this command only in a directory that matches the [default Shopify theme folder structure](https://shopify.dev/docs/themes/tools/cli#directory-structure).\n\n This command returns the following information:\n\n - A link to the [editor](https://shopify.dev/docs/themes/tools/online-editor) for the theme in the Shopify admin.\n - A [preview link](https://help.shopify.com/manual/online-store/themes/adding-themes#share-a-theme-preview-with-others) that you can share with others.\n\n If you use the `--json` flag, then theme information is returned in JSON format, which can be used as a machine-readable input for scripts or continuous integration.\n\n Sample output:\n\n ```json\n {\n \"theme\": {\n \"id\": 108267175958,\n \"name\": \"MyTheme\",\n \"role\": \"unpublished\",\n \"shop\": \"mystore.myshopify.com\",\n \"editor_url\": \"https://mystore.myshopify.com/admin/themes/108267175958/editor\",\n \"preview_url\": \"https://mystore.myshopify.com/?preview_theme_id=108267175958\"\n }\n }\n ```\n ",
Expand Down Expand Up @@ -5815,6 +5829,14 @@
"name": "json",
"type": "boolean"
},
"legacy": {
"allowNo": false,
"description": "Use the legacy Ruby implementation for the `shopify theme push` command.",
"env": "SHOPIFY_FLAG_LEGACY",
"hidden": true,
"name": "legacy",
"type": "boolean"
},
"live": {
"allowNo": false,
"char": "l",
Expand Down
114 changes: 114 additions & 0 deletions packages/theme/src/cli/commands/theme/push.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import Push from './push.js'
import {DevelopmentThemeManager} from '../../utilities/development-theme-manager.js'
import {ensureThemeStore} from '../../utilities/theme-store.js'
import {getDevelopmentTheme, removeDevelopmentTheme, setDevelopmentTheme} from '../../services/local-storage.js'
import {describe, vi, expect, test, beforeEach} from 'vitest'
import {Config} from '@oclif/core'
import {execCLI2} from '@shopify/cli-kit/node/ruby'
import {AdminSession, ensureAuthenticatedThemes} from '@shopify/cli-kit/node/session'
import {buildTheme} from '@shopify/cli-kit/node/themes/factories'

vi.mock('../../services/push.js')
vi.mock('../../utilities/theme-store.js')
vi.mock('../../utilities/theme-selector.js')
vi.mock('../../services/local-storage.js')
vi.mock('@shopify/cli-kit/node/ruby')
vi.mock('@shopify/cli-kit/node/session')
vi.mock('@shopify/cli-kit/node/themes/api')
vi.mock('@shopify/cli-kit/node/ui')

const CommandConfig = new Config({root: __dirname})

describe('Push', () => {
const adminSession = {token: '', storeFqdn: ''}
const path = '/my-theme'

beforeEach(() => {
vi.mocked(getDevelopmentTheme).mockImplementation(() => undefined)
vi.mocked(setDevelopmentTheme).mockImplementation(() => undefined)
vi.mocked(removeDevelopmentTheme).mockImplementation(() => undefined)
})

describe('run with Ruby implementation', () => {
test('should pass development theme from local storage to Ruby implementation', async () => {
// Given
const theme = buildTheme({id: 1, name: 'Theme', role: 'development'})!
vi.spyOn(DevelopmentThemeManager.prototype, 'findOrCreate').mockResolvedValue(theme)
vi.spyOn(DevelopmentThemeManager.prototype, 'fetch').mockResolvedValue(theme)
await run([])

// Then
expect(DevelopmentThemeManager.prototype.findOrCreate).not.toHaveBeenCalled()
expect(DevelopmentThemeManager.prototype.fetch).toHaveBeenCalledOnce()
expectCLI2ToHaveBeenCalledWith(`theme push ${path} --development-theme-id ${theme.id}`)
})

test('should pass theme and development theme from local storage to Ruby implementation', async () => {
// Given
const themeId = 2
const theme = buildTheme({id: 3, name: 'Theme', role: 'development'})!
vi.spyOn(DevelopmentThemeManager.prototype, 'findOrCreate').mockResolvedValue(theme)
vi.spyOn(DevelopmentThemeManager.prototype, 'fetch').mockResolvedValue(theme)
await run([`--theme=${themeId}`])

// Then
expectCLI2ToHaveBeenCalledWith(`theme push ${path} --theme ${themeId} --development-theme-id ${theme.id}`)
})

test('should not pass development theme to Ruby implementation if local storage is empty', async () => {
// When
await run([])

// Then
expect(DevelopmentThemeManager.prototype.findOrCreate).not.toHaveBeenCalled()
expect(DevelopmentThemeManager.prototype.fetch).toHaveBeenCalledOnce()
expectCLI2ToHaveBeenCalledWith(`theme push ${path}`)
})

test('should pass theme and development theme to Ruby implementation', async () => {
// Given
const theme = buildTheme({id: 4, name: 'Theme', role: 'development'})!
vi.spyOn(DevelopmentThemeManager.prototype, 'findOrCreate').mockResolvedValue(theme)
vi.spyOn(DevelopmentThemeManager.prototype, 'fetch').mockResolvedValue(theme)
await run(['--development'])

// Then
expect(DevelopmentThemeManager.prototype.findOrCreate).toHaveBeenCalledOnce()
expect(DevelopmentThemeManager.prototype.fetch).not.toHaveBeenCalled()
expectCLI2ToHaveBeenCalledWith(`theme push ${path} --theme ${theme.id} --development-theme-id ${theme.id}`)
})

test('should run the Ruby implementation if the password flag is provided', async () => {
// Given
const theme = buildTheme({id: 1, name: 'Theme', role: 'development'})!
vi.spyOn(DevelopmentThemeManager.prototype, 'fetch').mockResolvedValue(theme)

// When
await runPushCommand(['--password', '123'], path, adminSession)

// Then
expectCLI2ToHaveBeenCalledWith(`theme push ${path} --development-theme-id ${theme.id}`)
})
})

async function run(argv: string[]) {
await runPushCommand(['--legacy', ...argv], path, adminSession)
}

function expectCLI2ToHaveBeenCalledWith(command: string) {
expect(execCLI2).toHaveBeenCalledWith(command.split(' '), {
store: 'example.myshopify.com',
adminToken: adminSession.token,
})
}

async function runPushCommand(argv: string[], path: string, adminSession: AdminSession) {
vi.mocked(ensureThemeStore).mockReturnValue('example.myshopify.com')
vi.mocked(ensureAuthenticatedThemes).mockResolvedValue(adminSession)

await CommandConfig.load()
const push = new Push([`--path=${path}`, ...argv], CommandConfig)

await push.run()
}
})
77 changes: 76 additions & 1 deletion packages/theme/src/cli/commands/theme/push.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import {themeFlags} from '../../flags.js'
import ThemeCommand from '../../utilities/theme-command.js'
import {ensureThemeStore} from '../../utilities/theme-store.js'
import ThemeCommand, {FlagValues} from '../../utilities/theme-command.js'
import {DevelopmentThemeManager} from '../../utilities/development-theme-manager.js'
import {push, PushFlags} from '../../services/push.js'
import {hasRequiredThemeDirectories} from '../../utilities/theme-fs.js'
import {currentDirectoryConfirmed} from '../../utilities/theme-ui.js'
import {showEmbeddedCLIWarning} from '../../utilities/embedded-cli-warning.js'
import {Flags} from '@oclif/core'
import {globalFlags} from '@shopify/cli-kit/node/cli'
import {ensureAuthenticatedThemes} from '@shopify/cli-kit/node/session'
import {cwd, resolvePath} from '@shopify/cli-kit/node/path'
import {useEmbeddedThemeCLI} from '@shopify/cli-kit/node/context/local'
import {execCLI2} from '@shopify/cli-kit/node/ruby'

export default class Push extends ThemeCommand {
static summary = 'Uploads your local theme files to the connected store, overwriting the remote version if specified.'
Expand Down Expand Up @@ -95,6 +104,11 @@ export default class Push extends ThemeCommand {
description: 'Publish as the live theme after uploading.',
env: 'SHOPIFY_FLAG_PUBLISH',
}),
legacy: Flags.boolean({
hidden: true,
description: 'Use the legacy Ruby implementation for the `shopify theme push` command.',
env: 'SHOPIFY_FLAG_LEGACY',
}),
force: Flags.boolean({
hidden: true,
char: 'f',
Expand All @@ -103,9 +117,29 @@ export default class Push extends ThemeCommand {
}),
}

static cli2Flags = [
'theme',
'development',
'live',
'unpublished',
'nodelete',
'only',
'ignore',
'json',
'allow-live',
'publish',
'force',
'development-theme-id',
]

async run(): Promise<void> {
const {flags} = await this.parse(Push)

if (flags.password || flags.legacy) {
await this.execLegacyPush()
return
}

const pushFlags: PushFlags = {
path: flags.path,
password: flags.password,
Expand All @@ -128,4 +162,45 @@ export default class Push extends ThemeCommand {

await push(pushFlags)
}

async execLegacyPush() {
const {flags} = await this.parse(Push)
const path = flags.path || cwd()
const force = flags.force || false

const store = ensureThemeStore({store: flags.store})
const adminSession = await ensureAuthenticatedThemes(store, flags.password)

const workingDirectory = path ? resolvePath(path) : cwd()
if (!(await hasRequiredThemeDirectories(workingDirectory)) && !(await currentDirectoryConfirmed(force))) {
return
}

const flagsForCli2 = flags as typeof flags & FlagValues

showEmbeddedCLIWarning()

const developmentThemeManager = new DevelopmentThemeManager(adminSession)

const targetTheme = await (flagsForCli2.development
? developmentThemeManager.findOrCreate()
: developmentThemeManager.fetch())

if (targetTheme) {
if (flagsForCli2.development) {
flagsForCli2.theme = `${targetTheme.id}`
flagsForCli2.development = false
}
if (useEmbeddedThemeCLI()) {
flagsForCli2['development-theme-id'] = targetTheme.id
}
}

const flagsToPass = this.passThroughFlags(flagsForCli2, {
allowedFlags: Push.cli2Flags,
})
const command = ['theme', 'push', flagsForCli2.path, ...flagsToPass]

await execCLI2(command, {store, adminToken: adminSession.token})
}
}
Loading