Skip to content

Allow theme push and share commands to be called with multiple environments - #6361

Merged
dejmedus merged 8 commits into
mainfrom
jb-multi-env-push-share
Sep 23, 2025
Merged

dejmedus merged 8 commits into
mainfrom
jb-multi-env-push-share

Conversation

@dejmedus

@dejmedus dejmedus commented Sep 5, 2025 •

Copy link
Copy Markdown
Contributor

WHY are these changes introduced?

Previously theme push and share (which is push behind the scenes) could only be run with a single --environment flag. This PR allows them to be run with multiple environments in a single command

This refactor was also a good opportunity to do some clean up as share was the last command with leftover references to CLI2 (references have already been removed from pull, push, and dev)

WHAT is this pull request doing?

  • Removes flag parsing from push and share commands in favour of ThemeCommand's run logic
  • During multi environment runs, the progress bar is no displayed
  • Since push is exported publicly through @shopify/cli to be used by packages like shopkeeper, an auth session is created if one is not passed in from ThemeCommand (as one wouldn't be when push is used programatically)
  • Normalizes paths provided by shopify.theme.toml the same way as they would be via CLI
  • Removes references to passThroughFlags and cli2Flags as they are no longer used
push
new-push-demo.mov
push as imported function
shopkeeper-demo.mov
push jq output push-json-jq
share
new-share-demo.mov
Allow live error allow-live-error

How to test your changes?

  • Pull down the branch gh pr checkout 6361 or install the snapit version
npm i -g --@shopify:registry=https://registry.npmjs.org @shopify/cli@0.0.0-snapshot-20250912171601
  • Add a shopify.theme.toml file
Example toml
[environments.store1]
theme = "<theme>"
store = "<store>"
password  = "<shptka_password>"
path = "path/to/theme"

[environments.store2]
theme = "<theme>"
store = "<store>"
password  = "<shptka_password>"
path = "path/to/theme"
shopify theme push -e store1 -e store2
shopify theme share -e store1 -e store2
  • push and share should work as expected with no, single, and multiple --environment flags
  • The environment name should be displayed on output
  • push should error if allow-live is not provided while pushing to the live theme

Measuring impact

  • Existing analytics will cater for this addition

Checklist

  • I've considered possible cross-platform impacts (Mac, Linux, Windows)
  • I've considered possible documentation changes

@github-actions

github-actions Bot commented Sep 5, 2025 •

Copy link
Copy Markdown
Contributor

Coverage report

St.❔
Category Percentage Covered / Total
🟡 Statements
78.68% (+0.06% 🔼)
13633/17327
🟡 Branches
72.67% (+0.04% 🔼)
6636/9132
🟡 Functions
78.77% (-0.09% 🔻)
3522/4471
🟡 Lines
79.02% (+0.06% 🔼)
12883/16303
Show files with reduced coverage 🔻
St.❔
File Statements Branches Functions Lines
🟢
... / Dev.tsx
90.59% (-2.35% 🔻)
75% (-1.79% 🔻)
86.36% (-4.55% 🔻)
92.5% (-1.25% 🔻)
🔴
... / app-management-client.ts
53.45% (-0.32% 🔻)
47.06% (-0.62% 🔻)
49.09% (-0.45% 🔻)
52.26% (-0.17% 🔻)
🟢
... / ConcurrentOutput.tsx
98.36% (-1.64% 🔻)
92% (-4% 🔻)
100%
98.33% (-1.67% 🔻)
🟢
... / push.ts
95.06% (+0.26% 🔼)
77.63% (-3.01% 🔻)
100%
95% (+0.26% 🔼)
🔴
... / theme-ui.ts
59.09% (+11.72% 🔼)
38.89% (+2.53% 🔼)
60% (-20% 🔻)
59.09% (+11.72% 🔼)
🟢
... / theme-uploader.ts
91.67% (-1.59% 🔻)
78.33% (-2.02% 🔻)
87.5% (-4.69% 🔻)
92.55% (-1.79% 🔻)

Test suite run success

3323 tests passing in 1384 suites.

Report generated by 🧪jest coverage report action from 47d198d

const environment = flags.environment

// when push is used programmatically, we don't have an admin session, so need to create one
const session =

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

return true
}

if (multiEnvironment) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Since multi env commands are non-interactive, trying to push to a live theme without allow-live will prevent uploads and error

export type RequiredFlags = (string | string[])[] | null

export default abstract class ThemeCommand extends Command {
passThroughFlags(flags: FlagValues, {allowedFlags}: PassThroughFlagsOptions): string[] {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I believe this it leftover from CLI2, it was used in pull/push/share but there doesnt seem to be any references to it now

passThroughFlags

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Right, those were flags we passed to the CLI2 when we used it internally during the migration.

@dejmedus

dejmedus commented Sep 5, 2025

Copy link
Copy Markdown
Contributor Author

/snapit

@Shopify Shopify deleted a comment from github-actions Bot Sep 11, 2025
@dejmedus
dejmedus force-pushed the jb-multi-env-push-share branch 3 times, most recently from 2f6709a to e873ee1 Compare September 11, 2025 23:04
@dejmedus

Copy link
Copy Markdown
Contributor Author

/snapit

/**
* Initiates the push process based on provided flags.
*
* @param flags - The flags for the push operation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I couldn't decide if it would be helpful to add the new params to the docstring - when the function is used programmatically likely only flags would be passed in

return true
}

if (multiEnvironment) {

@dejmedus dejmedus Sep 12, 2025 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

In multi environment commands we're returning false if a user can't confirm to align with other multi env command behaviour. However, in single/no env runs we're returning true and continuing

@dejmedus

Copy link
Copy Markdown
Contributor Author

/snapit

@github-actions

Copy link
Copy Markdown
Contributor

🫰✨ Thanks @dejmedus! Your snapshot has been published to npm.

Test the snapshot by installing your package globally:

npm i -g --@shopify:registry=https://registry.npmjs.org @shopify/cli@0.0.0-snapshot-20250912171601

Caution

After installing, validate the version by running just shopify in your terminal.
If the versions don't match, you might have multiple global instances installed.
Use which shopify to find out which one you are running and uninstall it.

@dejmedus
dejmedus marked this pull request as ready for review September 12, 2025 17:40
@dejmedus
dejmedus requested review from a team as code owners September 12, 2025 17:40
@dejmedus
dejmedus force-pushed the jb-multi-env-push-share branch from e986528 to d44bc9e Compare September 12, 2025 18:42
@graygilmore

Copy link
Copy Markdown
Contributor

/snapit

@github-actions

Copy link
Copy Markdown
Contributor

🫰✨ Thanks @graygilmore! Your snapshot has been published to npm.

Test the snapshot by installing your package globally:

npm i -g --@shopify:registry=https://registry.npmjs.org @shopify/cli@0.0.0-snapshot-20250916211333

Caution

After installing, validate the version by running just shopify in your terminal.
If the versions don't match, you might have multiple global instances installed.
Use which shopify to find out which one you are running and uninstall it.

@graygilmore graygilmore left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎩 went well! Only one real comment here around how we render the flags. Since it's a larger change let's make sure we get another person from the team to look at it.

onComplete?: (ctx: TContext) => void
abortSignal?: AbortSignal
noColor?: boolean
noProgressBar?: boolean

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

(nit) generally prefer positive booleans like progressBar so that logic reads a little nicer than !noX which takes the brain a little bit of work but since noColor is already there maybe it's fine to leave it.

Comment thread packages/theme/src/cli/services/push.ts Outdated
selectedTheme,
session,
{
path: workingDirectory,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

(super nit) since we're already touching this, feel free to alphabetize

const usedFlag = Array.isArray(flag) ? flag.find((flag) => flags[flag]) : flag
return usedFlag && [usedFlag.includes('password') ? usedFlag : `${usedFlag}: ${flags[usedFlag]}`]
if (usedFlag === 'password') return `password`
if (usedFlag === 'path' && typeof flags.path === 'string') return `path: ..${flags.path.slice(-12)}`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Since path will always be set I wonder if we should tweak this a bit? If you had a long folder name it'd be pretty cut off and you might not know what it's saying (especially since .. has special meaning in the CLI when working with paths.

Maybe we skip adding this one entirely unless it's explicitly provided?

Just in case it's not expected: I don't have path in my shopify.theme.toml for either environment I'm testing this on and I see:

➜  aurora-theme git:(main) shopify theme push -e aurora -e farm --allow-live
?  Run push in the following environments?

   ┃  Environment
   ┃  • aurora store: <shop1>.com, password, path: ..aurora-theme, theme: <id>
   ┃  • farm store: <shop2>.myshopify.com, password, path: ..aurora-theme, theme: <id>

@dejmedus dejmedus Sep 18, 2025 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Previously default flag values (like path = cwd) were being allowed to meet flag requirements. During check PR review it was decided that this shouldn't be allowed, so once that validation change merges in we will need to explicitly provide a path. I should have stacked the PRs smarter!

I agree that the truncated path looks a bit strange, could just go back to full paths or maybe it would make more sense to only show the the last / segment 🤔

new-path-truncation

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤔 yeah I'm not sure. Full paths might be gnarly here. I like showing the last / segment but we need to make sure it's clear that we're clipping the path.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That makes sense! I've updated it to match the screenshot: ... and the last segment. Hopefully the three dots communicate truncation well-ish, but i'm open to changing it

const {progress: uploadProgress, promise: uploadPromise} = await uploadJobPromise

const timeout = options.multiEnvironment ? 4000 : 1000
//

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
//

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thank you! 🙏🏼

EvilGenius13 and others added 5 commits September 18, 2025 10:41
`themeUploader` displays theme sync progress with
an Ink animation. When multiple are ran at the same
time, like in multi environment commands, it can
behave unexpectedly. This commit creates alternative
uploaders to prevent this
Previously, any multi environment `path` defined in
`shopify.theme.toml` would not be normalized as
that happened when CLI flags were parsed.
This commit calls `resolvePath` when paths are
loaded in from an environment
References to CLI2 have already been removed from
`pull`, `push`, and `dev`. This commit removes the
last remaining unused reference in `share`.

`passThroughFlags` is not being used by any command
so it has also been removed
When a command is run with multiple environment flags
we cant prompt for confirmation. This commit allows
`ensureDirectoryConfirmed` to accept environment
and multiEnvironment params

When a multi environment command run seems
to be acting on an invalid directory an error is
display and we do not confirm the action

This matches the behavior of other instances where
multi environment command actions cant be confirmed
Previously theme `push` and `share` commands could
only be run in a single environment at a time. This
commit allows them to be run in multiple environments
in a single command run. Output is prefixed with the environment
name, when one exists
CLI commands push and pull are exported publicly
through @shopify/cli so external packages (like
shopkeeper) can use them. When push is used
programmatically we need to authenticate inside
the function as we wont have access to the session
created by ThemeCommand
@dejmedus
dejmedus force-pushed the jb-multi-env-push-share branch from d44bc9e to 2fbc81e Compare September 18, 2025 21:01
@dejmedus

Copy link
Copy Markdown
Contributor Author

Fixed a conflict as progress bar has been split out from the Tasks component. noProgressBar is now being passed down to LoadingBar and its test has been moved as well

@graygilmore

Copy link
Copy Markdown
Contributor

/snapit

@github-actions

Copy link
Copy Markdown
Contributor

🫰✨ Thanks @graygilmore! Your snapshot has been published to npm.

Test the snapshot by installing your package globally:

npm i -g --@shopify:registry=https://registry.npmjs.org @shopify/cli@0.0.0-snapshot-20250918231536

Caution

After installing, validate the version by running just shopify in your terminal.
If the versions don't match, you might have multiple global instances installed.
Use which shopify to find out which one you are running and uninstall it.

if (usedFlag === 'password') return `password`
if (usedFlag === 'path' && typeof flags.path === 'string') {
const lastSegment = flags.path.split('/').pop() || flags.path
return `path: .../${lastSegment}`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

(this might be too much extra for this PR, feel free to ignore)

It might be best to include both the beginning and end. Something like:

~/.../folder-name
/Something/.../folder-name

What do you think? Here's some code example:

const flags = [
  'single-folder',
  '~/Users/graygilmore/repos/aurora-theme',
  'C:\\SuperCoolDriveName\\LintingRules\\pipeline-rules\\.theme-check.yml',
  '/home/runner/work/Shopify/Shopify/tools/something-else-here/cool_folder',
]

flags.forEach((flag) => {
  const splits = flag.split(/[/|\\]/);

  if (splits.length === 1) {
    console.log(flag)
    return
  }

  const first = splits[0] === '' ? `/${splits[1]}` : splits[0]
  const last = splits.at(-1)

  console.log(`${first}/.../${last}`)
})

Which would output:

single-folder
~/.../aurora-theme
C:/.../.theme-check.yml
/home/.../cool_folder

I'm not 100% sure if we need to specifically handle \ for Windows here.

@dejmedus dejmedus Sep 19, 2025 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I like this a lot! The only difference would be that the path has already been resolved, so relative single-folder would look something like Users/.../single-folder and same with ~

Updated: paths


const {progress: uploadProgress, promise: uploadPromise} = await uploadJobPromise

const timeout = options.multiEnvironment ? 4000 : 1000

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

How did you pick the 4 second timeout for multi environment?
Side nit - although it was called timeout before, can we call it something like updateInterval if this is just the delay to update the progress to the terminal?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Will do! I increased it to try to get closer to 25% jumps each time. At 1000 the logs tended to fill up shorter terminal windows, which i'm not totally against if we do want to change back.

@EvilGenius13 EvilGenius13 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Awesome work! 🎩 went well

@dejmedus
dejmedus force-pushed the jb-multi-env-push-share branch from 7acfe20 to 47d198d Compare September 19, 2025 21:50
@github-actions

Copy link
Copy Markdown
Contributor

Differences in type declarations

We detected differences in the type declarations generated by Typescript for this branch compared to the baseline ('main' branch). Please, review them to ensure they are backward-compatible. Here are some important things to keep in mind:

  • Some seemingly private modules might be re-exported through public modules.
  • If the branch is behind main you might see odd diffs, rebase main into this branch.

New type declarations

We found no new type declarations in this PR

Existing type declarations

packages/cli-kit/dist/public/node/ui.d.ts
@@ -322,6 +322,7 @@ interface RenderTableOptions<T extends ScalarDict> extends TableProps<T> {
 export declare function renderTable<T extends ScalarDict>({ renderOptions, ...props }: RenderTableOptions<T>): string | undefined;
 interface RenderTasksOptions {
     renderOptions?: RenderOptions;
+    noProgressBar?: boolean;
 }
 /**
  * Runs async tasks and displays their progress to the console.
@@ -329,7 +330,7 @@ interface RenderTasksOptions {
  * ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
  * Installing dependencies ...
  */
-export declare function renderTasks<TContext>(tasks: Task<TContext>[], { renderOptions }?: RenderTasksOptions): Promise<TContext>;
+export declare function renderTasks<TContext>(tasks: Task<TContext>[], { renderOptions, noProgressBar }?: RenderTasksOptions): Promise<TContext>;
 /**
  * Awaits a single promise and displays a loading bar while it's in progress. The promise's result is returned.
  * @param options - Configuration object
packages/cli-kit/dist/private/node/ui/components/LoadingBar.d.ts
@@ -2,6 +2,7 @@ import React from 'react';
 interface LoadingBarProps {
     title: string;
     noColor?: boolean;
+    noProgressBar?: boolean;
 }
-declare const LoadingBar: ({ title, noColor }: React.PropsWithChildren<LoadingBarProps>) => JSX.Element;
+declare const LoadingBar: ({ title, noColor, noProgressBar }: React.PropsWithChildren<LoadingBarProps>) => JSX.Element;
 export { LoadingBar };
\ No newline at end of file
packages/cli-kit/dist/private/node/ui/components/Tasks.d.ts
@@ -14,6 +14,7 @@ interface TasksProps<TContext> {
     onComplete?: (ctx: TContext) => void;
     abortSignal?: AbortSignal;
     noColor?: boolean;
+    noProgressBar?: boolean;
 }
-declare function Tasks<TContext>({ tasks, silent, onComplete, abortSignal, noColor, }: React.PropsWithChildren<TasksProps<TContext>>): JSX.Element | null;
+declare function Tasks<TContext>({ tasks, silent, onComplete, abortSignal, noColor, noProgressBar, }: React.PropsWithChildren<TasksProps<TContext>>): JSX.Element | null;
 export { Tasks };
\ No newline at end of file

@dejmedus
dejmedus added this pull request to the merge queue Sep 23, 2025
Merged via the queue into main with commit 0f5ba55 Sep 23, 2025
2 checks passed
@dejmedus
dejmedus deleted the jb-multi-env-push-share branch September 23, 2025 14:57
@andershagbard

Copy link
Copy Markdown
Contributor

@dejmedus Have you considered implementing glob search, like Theme Kit has? *-production to target e.g. int-production and us-production

@dejmedus

dejmedus commented Oct 3, 2025

Copy link
Copy Markdown
Contributor Author

Hi @andershagbard 👋 that makes sense! I've created an issue for the feature here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Allow to push to multiple environments at once

5 participants