Shopify / cli

Build apps, themes, and hydrogen storefronts for Shopify
https://shopify.dev
MIT License
434 stars 128 forks source link

How to connect extension to more than one app? #1998

Closed avocadoslab closed 1 year ago

avocadoslab commented 3 years ago

Issue summary

shopify extension create allows extension to be created and connected to one Shopify app. In cases where you have development app and production app, how do we connect or register same extension with both apps?

shopify extension connect allows connecting only to previously selected development app so would love to know how to handle this case.

Expected behavior

As a developer, I should have flexibility of connecting/registering extension to more than one app in cases where we maintain development, staging & production app.

Actual behavior

Can't register already registered extension to new app.

catlee commented 3 years ago

I'd like to understand your use case a bit better. Can you expand on why you have development and production apps?

Are you aware that you can test draft versions of your extension on your dev shop?

avocadoslab commented 3 years ago

Yes @catlee, I'm aware that we can test draft version of extension on development store.

When we do local development of an app, you need to have app registered on partner portal in order to install app on development store. So we've created dev app which we use for all testing and validations before pushing new changes to production app.

Dev app is necessary in case where app is using app proxy because you will need different proxy URL while running locally vs while running on production for merchants.

Also in case of embedded app, navigation can vary when we developing a new feature all-together so having that separation is necessary so that live merchants don't see your new navigations.

ClaytonPassmore commented 3 years ago

Hey @catlee :wave: - wanted to jump into this conversation as well and share my perspective.

I'd like to understand your use case a bit better. Can you expand on why you have development and production apps?

Are you aware that you can test draft versions of your extension on your dev shop?

I truly hope this doesn't sound combative, but I'm a little shocked at the idea that one might expect an app to not have multiple instances. No company at any kind of reasonable size is writing code that gets put into a production environment without testing it on (at least) a local instance first.

Most interesting web-based applications have something that exists outside of the Shopify extension world - a server, a database, etc. Any changes to that infrastructure exists outside the versioning system of Shopify extensions and as such having multiple instances is the only way to do safe development work on an app.

Given that multiple app instances are seriously a must, the way these Shopify extensions are registered and updated becomes a workflow problem for developers (like @pratiknikam described, or like @who_khiers described on Shopify's Discord server).

If theme extensions make API calls to a web server, and that server has a different address based on whether you're in production, staging, local, etc - extensions make it a little difficult to manage. You're either forced to configure app proxies that point back at your different environments for each app instance (and now you're needlessly proxying requests through Shopify), or every single app instance has to have a different URL in its copy of the extension.

It also feels awkward to have to push extension changes for your production app (from an environment that has your production credentials) only to have to go in to the partner dash, make a new extension version and publish it. Not only that, but local development means that all changes to extensions need to be pushed by every developer with a separate app instance using the Shopify CLI. The use of both the CLI and the partner dash to publish the extension makes this feel like a hybrid of automation (i.e. scripting) and manual effort (i.e. pressing buttons in a web interface), which means you get the pains of both and the benefits of neither.

Now all that said, I've just started dabbling with app extensions and it's possible that I'm missing out on some critical elements. If I am, I'm very happy to be told so :smile:

khier996 commented 3 years ago

In addition to multiple env files for different apps, we also originally had Ruby env variables in our liquid templates. So, our files had .liquid.erb extensions. Now, we can't use them within theme-app-extensions folder. So, the current workaround we are thinking of is to have a file structure like this:

theme_app_extensions
  src
    blocks
      widget.liquid.erb
      ...
  dist
    production
      blocks
         widget.liquid
      .env
    staging
      blocks
         widget.liqud
      .env

And a script that will compile files in src folder multiple times into folders in the dist folder with different Ruby environments. .env files in dist folder will need to be set up once in the beginning. Then to push to different apps, you will need to go to the corresponding folder in dist, e.g. theme_app_extensions/dist/production and run shopify extension push

catlee commented 3 years ago

Thank you @ClaytonPassmore for your feedback, it's really helpful!

I truly hope this doesn't sound combative, but I'm a little shocked at the idea that one might expect an app to not have multiple instances. No company at any kind of reasonable size is writing code that gets put into a production environment without testing it on (at least) a local instance first.

Not combative at all! I fully appreciate the need to have staging/testing environments before pushing code to production. I agree that in this case, having multiple app instances is required since there is no way to have separate backend URLs, etc. for the different environments all represented within a single app.

For now the best workaround I can think of is to manage the .env files locally for the different environments. This could be achieved manually or via automation scripts similar to what @khier996 has suggested. This is clearly not a great workflow for you, so I will make sure to bring this up as a priority to my team.

The use of both the CLI and the partner dash to publish the extension makes this feel like a hybrid of automation (i.e. scripting) and manual effort (i.e. pressing buttons in a web interface), which means you get the pains of both and the benefits of neither.

Would it help to have CLI support for creating, publishing / unpublishing versions?

leighs-hammer commented 3 years ago

Just chiming in, to second most of the views on here. The multiple envs is creating a manual road block to workflows between the lack of env and reliance on manual intervention kinda stifles a fair few of the automated QA and CI pipelines. Really looking forward to more portability with the CLI at present it seems very single developer flow and that is tough in larger teams.

ClaytonPassmore commented 3 years ago

Thanks for the response @catlee! I appreciate you raising the workflow issue with your team.

Would it help to have CLI support for creating, publishing / unpublishing versions?

Yes, that would definitely help! That would (hopefully) allow us to integrate the process of publishing changes into our CD pipeline :+1:

gilesbutler commented 3 years ago

Just came across this as we're a small team experiencing the exact same issue.

Would love to be able to have different versions of a checkout extension for different apps/environments.

Adding the ability to use the shopify-cli in a CI environment would help solve some of these issues. We could use the current recommended setup for local testing. Then when we push to different branches the CI could run the shopify-cli to build and push to shopify for us. The CI could push to different prod and dev apps based on the env vars. At the moment I don't think this is possible due to the fact you can't login to the shopify-cli in a CI environment as the login command needs to open a browser.

hehoo commented 3 years ago

Keen to see the extension can connect to multiple apps. We also have local, staging apps for testing/development purpose before releasing new features to prod app. @catlee Do you have a timetable when you are going to finish this feature to enable the extension (post-purchase for our case) to be able to connect to multiple apps?

jonniedarko commented 3 years ago

I too need this as its difficult to switch between environments when testing

markovic-nikola commented 2 years ago

+1

brucelee90 commented 2 years ago

+1 I had the same issue with my app

axis80 commented 2 years ago

Here's how I solved this:

DanPete commented 2 years ago

Thanks for the solution @axis80! Took a lot of inspiration from you can came up with a pretty simple node script that can copy over theme extensions folders and files using recursive-copy and replace-in-file

Was thrown together quickly, so I am sure it could use some clean-up. 🙂

cconst copy = require('recursive-copy');
const replace = require('replace-in-file');

const themeExtensionFolder = 'theme-app-extension';

function buildExtension({ env = 'development' } = {}) {
  copy(
    `${themeExtensionFolder}/src`,
    `${themeExtensionFolder}/${env}`,
    { overwrite: true },
    function (error, results) {
      if (error) {
        console.error('Copy failed: ' + error);
      } else {
        console.info('Copied ' + results.length + ' files');
        if (env === 'production') {
          // If production build, assign development variables to production variable values
          replaceDevSpecificVars(env);
        }
      }
    }
  );
}

async function replaceDevSpecificVars() {
  const productionFolderName = 'production';
  const replacementPaths = [
    `${themeExtensionFolder}/${productionFolderName}/assets/index.js`,
    `${themeExtensionFolder}/${productionFolderName}/assets/global.js`,
  ];

  const options = {
    files: [...replacementPaths],
    from: /PROXY_SUBPATH = 'sale-dev'/g,
    to: "PROXY_SUBPATH = 'sale'",
  };

  try {
    const results = await replace(options);
    console.log('Replacement results:', results);
  } catch (error) {
    console.error('Error occurred:', error);
  }
}

buildExtension({ env: 'development' });
buildExtension({ env: 'production' });

I created that file in the root of the project and named it copy-theme-extensions.js, so I could just run node copy-theme-extensions.js or even add it as a script in package.json like "build:extension": "node copy-theme-extensions.js"

ElishaStaks commented 2 years ago

This is how I solved this issue:

Before following below make sure for each environment such as local, dev and prod apps in your shopify partners page all have a registered app extension. You will need to do this so you have valid credentials for those environments that your primary theme app extension env files can point to.

mikestone14 commented 2 years ago

Before following below make sure for each environment such as local, dev and prod apps in your shopify partners page all have a registered app extension. You will need to do this so you have valid credentials for those environments that your primary theme app extension env files can point to.

The biggest challenge we faced connecting apps across environments was registering the app extension for each app (staging app, production app, development apps). This was a challenge because once you run the Shopify CLI command, shopify extension create, and create the extension for one of your apps you cannot create or register additional apps. A workaround that we used for Theme App Extensions was to rename the Shopify-generated theme-app-extension directory before running the shopify extension create command again, which will allow you to create a new app extension attached to a Shopify app (and re-generates the theme-app-extension directory).

$ mv theme-app-extension theme-app-extension-local
$ shopify extension create

Select the Shopify app to which you would like to connect your extension, such as staging. Then copy your blocks/assets/etc from theme-app-extension-local to theme-app-extension.

$ cp -r theme-app-extension-local/blocks theme-app-extension
$ cp -r theme-app-extension-local/assets theme-app-extension
$ cp -r theme-app-extension-local/locales theme-app-extension
$ cp -r theme-app-extension-local/snippets theme-app-extension

Repeat this process for the other apps you would like to register. Once this is done, you can run shopify extension connect within the theme-app-extension directory, which will then give you the option to select from any of the apps you have registered and connect to it. You can also remove the various theme-app-extension-* directories created during this process.


More detailed write up here: https://dev.to/thegnarco/managing-shopify-app-extensions-across-environments-oga

github-actions[bot] commented 2 years ago

This issue seems inactive. If it's still relevant, please add a comment saying so. Otherwise, take no action.

→ If there's no activity within a week, then a bot will automatically close this.

Thanks for helping to improve Shopify's dev tooling and experience.

ClaytonPassmore commented 2 years ago

As of today, we still have no way of publishing from the CLI - publishing still requires clicking of buttons in a web UI.

Screen Shot 2022-09-02 at 9 23 45 AM

That is to say, the problems mentioned in this comment are still relevant.

adventuretocode commented 2 years ago

This is how I solved this issue:

Before following below make sure for each environment such as local, dev and prod apps in your shopify partners page all have a registered app extension. You will need to do this so you have valid credentials for those environments that your primary theme app extension env files can point to.

  • Create package json file using command yarn init (install yarn if you haven't already).
  • Create env files for whatever environments you have in my case i just created a dev and prod.
  • Copy and past the content of the .env file that gets generated initially and paste it in your new env files with the correct credentials.
SHOPIFY_API_KEY={{ ENV_API_KEY }}
SHOPIFY_API_SECRET={{ ENV_API_SECRET}}
EXTENSION_TITLE=theme-app-extension
EXTENSION_ID={{ ENV_EXTENSION_ID }}
EXTENSION_UUID={{ ENV_EXTENSION_UUID }}
  • Inside the package.json file add these commands to the scripts tag for each env you would like to publish to. These commands will simply change to the directory where the theme-app-extension is and it will copy the desired env file to .env and push those changes. After that it will delete the .env file that was just generated to clean up.
scripts": {
    "publish:dev": "cd theme-app-extension && cp .env.dev .env && shopify extension push && rm .env",
    "publish:prod": "cd theme-app-extension && cp .env.prod .env && shopify extension push && rm .env",
    "publish:local": "cd theme-app-extension && cp .env.local .env && shopify extension push && rm .env"
  }
  • In the root folder of your directory where the theme-app-extension lies simply run one of the commands such as yarn publish:dev to push to your desired app.

This best way to manage the env @ElishaStaks Thanks lot's

Jellyfishboy commented 1 year ago

+1

Considering my experience with Shopify developer support so far, I wouldn't bank on this feature being added any time soon.

github-actions[bot] commented 1 year ago

This issue seems inactive. If it's still relevant, please add a comment saying so. Otherwise, take no action.

→ If there's no activity within a week, then a bot will automatically close this.

Thanks for helping to improve Shopify's dev tooling and experience.

ClaytonPassmore commented 1 year ago

This issue is still relevant.

Jellyfishboy commented 1 year ago

Very relevant, @shopify-admins have yet to comment. Please provide an update rather than being silent.

AdityaMalani commented 1 year ago

A very simple solution that worked for me.

Always run npm run deploy -- --reset to deploy the theme app. This allows you to choose to which app you want to connect and deploy the app extension to.

This DOES NOT change the saved config for the npm run dev command, which means that when you run npm run dev, app extension gets pushed to the development app only.

gurkanoluc commented 1 year ago

@AdityaMalani you are a star!

neok commented 1 year ago

This fixes the issue https://github.com/Shopify/cli/issues/1998 sometimes may need to change env by running this command:

export SHOPIFY_API_KEY='your key'
bhr commented 1 year ago

We've solved the issue on our end as follows:

There's a .env file with each environment variable to be replaced.

Package.json

{
  "config": {
    "API_KEY_PROD": "ABC", // replace with your API key
    "API_KEY_DEV": "DEF" // replace with your API key
  },
  "scripts": {
    "shopify:ext:prepare": "node extensions/prepare.mjs",
    "shopify:ext:cleanup": "node extensions/cleanup.mjs",
    "shopify:deploy:run": "shopify app deploy --force",
    "shopify:deploy:prod": "yarn shopify:ext:prepare prod && yarn shopify:deploy:run --api-key=$npm_package_config_API_KEY_PROD && yarn shopify:ext:cleanup",
    "shopify:deploy:dev": "yarn shopify:ext:prepare dev && yarn shopify:deploy:run --api-key=$npm_package_config_API_KEY_DEV && yarn shopify:ext:cleanup"
  },
  "devDependencies": {
    "@shopify/app": "^3.45.4",
    "@shopify/cli": "^3.45.4"
  }
}

Prepare.mjs - replace variables with run-time versions

import { execSync } from 'child_process';
import { config } from 'dotenv';
import { readFileSync, writeFileSync } from 'fs';
import { dirname } from 'path';
import { fileURLToPath } from 'url';

const _dirname = dirname(fileURLToPath(import.meta.url));

const environment = process.argv[2];

if (environment === 'dev') {
  config({ path: `${_dirname}/.env.development` });
} else if (environment === 'prod') {
  config({ path: `${_dirname}/.env` });
} else {
  throw new Error(`Unknown env ${environment}`);
}

// copy all files from current directory into dist
const distPath = `${_dirname}/dist`;

const shell = execSync;
shell(`rm -rf ${distPath}`);
shell(`cp -r ${_dirname} ${distPath}`);

// read file and replace ENV Var
const filePath = `${distPath}/widget-ext/blocks/widget-embed.liquid`;
const buf = readFileSync(filePath);
const fileString = buf.toString();
const newString = fileString.replace('$_{MY_ENV_VAR}', process.env.MY_ENV_VAR);
writeFileSync(filePath, newString);

Cleanup.mjs - remove dist directory

import { execSync } from 'child_process';
import { dirname } from 'path';
import { fileURLToPath } from 'url';

const _dirname = dirname(fileURLToPath(import.meta.url));

// copy all files from current directory into dist
const distPath = `${_dirname}/dist`;

const shell = execSync;
shell(`rm -rf ${distPath}`);

Shopify.app.toml

extension_directories = ["extensions/dist/*"]
github-actions[bot] commented 1 year ago

This issue seems inactive. If it's still relevant, please add a comment saying so. Otherwise, take no action. → If there's no activity within a week, then a bot will automatically close this. Thanks for helping to improve Shopify's dev tooling and experience.

P.S. You can learn more about why we stale issues here.

adventuretocode commented 1 year ago

Hi All from 31 may 2023 Shopify CLI 2.x was sunset I will suggest to use Shopify CLI 3.x it was very easy to connect multiple theme app extension.

https://github.com/Shopify/shopify-app-template-node/blob/main/package.json

` npm run npm run dev -- --reset # rest shopify account

npm run info

"scripts": { "shopify": "shopify", "build": "shopify app build", "dev": "shopify app dev", "info": "shopify app info", "generate": "shopify app generate", "deploy": "shopify app deploy" }, `

github-actions[bot] commented 1 year ago

This issue seems inactive. If it's still relevant, please add a comment saying so. Otherwise, take no action. → If there's no activity within a week, then a bot will automatically close this. Thanks for helping to improve Shopify's dev tooling and experience.

P.S. You can learn more about why we stale issues here.

ClaytonPassmore commented 1 year ago

If we're learning to live with it, does that make it inactive? Think on it, @github-actions bot and let me know what you come up with.

adventuretocode commented 1 year ago

@all please use the Shopify Cli 3.0 "@shopify/app": "^3.47.5", "@shopify/cli": "^3.47.5", "@shopify/cli-kit": "^3.47.5", it was wasy to connect same "theme app extension " to many app just you need to --reset and select app.

it was really good. Thanks to shopify team @shopify-admins

Arkham commented 1 year ago

Hey folks, we've recently released 3.48.0 which includes support for connecting your source to multiple apps. Check out the documentation here:

The CLI also supports creating a live version with app deploy and an unreleased version with app deploy --no-release. You can read more about simplified deployments here: https://shopify.dev/docs/apps/deployment/extension/simplified

I'm going to close this, but feel free to open new issues. Thanks for reporting and for your patience!