Code Shopify About

Shopify App and Theme Development tutorials for those who are familiar with code and want to dive into Shopify.

Sign up for Shopify

Making Slate work with M1 Mac

Monday, Nov 29, 2021

With the release of new Apple silicon chips, this new architecture has lead to some issues with Slate failing on M1 Macs. However, since Slate is no longer supported by Shopify, it is unlikely we'll see an official fix. Follow this guide to fix the issue:

Open your terminal app using Rosetta

You may need to set your terminal app to open using Rosetta. More info on why can be found here:

Rosetta terminal

Update Themekit Version

This solution is based on a discussion here regarding a crash when trying to install/run Slate on M1 Macs due to the dependency on @shopify/themekit v0.6 in slate-tools and slate-sync.

Adding a resolutions block to update to the latest themekit version in package.json appears to fix the issue.

Add the following to your project's package.json, below the dependencies block:


"resolutions": {
  "**/@shopify/themekit": "^1.1.9"
}
  

An issue may still remain: It looks like the themekit api changed when compared to v0.6.x. We will also need to manually update one of the node_modules found in @shopify/slate-sync.

This change effects how configs are send to themekit. Replace the entirety of `node_modules/@shopify/slate-sync/index.js` with the following snippet so it looks like this: (The changes were based on this PR):


const chalk = require('chalk');
const figures = require('figures');
const https = require('https');
const themekit = require('@shopify/themekit');
const slateEnv = require('@shopify/slate-env');
const SlateConfig = require('@shopify/slate-config');
    
const config = new SlateConfig(require('./slate-sync.schema'));
    
let deploying = false;
let filesToDeploy = [];
    
function maybeDeploy() {
  if (deploying) {
    return Promise.reject(new Error('Deploy already in progress.'));
  }
    
  if (filesToDeploy.length) {
    const files = [...filesToDeploy];
    filesToDeploy = [];
    return deploy('upload', files);
  }
    
  return Promise.resolve();
}
    
function _validateEnvValues() {
  const result = slateEnv.validate();
    
  if (!result.isValid) {
    console.log(
      chalk.red(
        `Some values in environment '${slateEnv.getEnvNameValue()}' are invalid:`,
      ),
    );
   result.errors.forEach((error) => {
    console.log(chalk.red(`- ${error}`));
  });
    
    process.exit(1);
  }
 }
    
function _generateConfigFlags(cmd) {
  _validateEnvValues();
    
  const flags = {
    password: slateEnv.getPasswordValue(),
    themeid: slateEnv.getThemeIdValue(),
    store: slateEnv.getStoreValue(),
    env: slateEnv.getEnvNameValue(),
  };
      
   if (cmd === 'upload') {
     flags.nodelete = true;
  }
    
  if (slateEnv.getTimeoutValue()) {
    flags.timeout = slateEnv.getTimeoutValue();
  }
  if (slateEnv.getIgnoreFilesValue()) {
    flags.ignoredFiles = slateEnv.getIgnoreFilesValue().split(':');
  }
    
    // Convert object to key value pairs and flatten the array
    return flags;
}
    
/**
* Deploy to Shopify using themekit.
*
* @param   cmd     String    The command to run
* @param   files   Array     An array of files to deploy
* @return          Promise
*/
async function deploy(cmd = '', files = []) {
  if (!['upload', 'replace'].includes(cmd)) {
    throw new Error(
      'shopify-deploy.deploy() first argument must be either "upload", "replace"',
    );
}
    
deploying = true;
    
console.log(chalk.magenta(`\n${figures.arrowUp}  Uploading to Shopify...\n`));
    
try {
  await themekit.command('configure', _generateConfigFlags(), {
    cwd: config.get('paths.theme.dist'),
  });
  await themekit.command(
    'deploy',
    {
      ..._generateConfigFlags(cmd),
      files,
    },
    {
      cwd: config.get('paths.theme.dist'),
    },
  );
} catch (error) {
  console.error('My Error', error);
}
    
deploying = false;
    
  return maybeDeploy;
}
    
/**
* Fetch the main theme ID from Shopify
*
* @param   env   String  The environment to check against
* @return        Promise Reason for abort or the main theme ID
*/
function fetchMainThemeId() {
  _validateEnvValues();
    
  return new Promise((resolve, reject) => {
    https.get(
      {
        hostname: slateEnv.getStoreValue(),
        path: '/admin/themes.json',
        auth: `:${slateEnv.getPasswordValue}`,
        agent: false,
        headers: {
          X-Shopify-Access-Token': slateEnv.getPasswordValue(),
        },
      },
    (res) => {
      let body = '';
    
      res.on('data', (datum) => (body += datum));
    
      res.on('end', () => {
        const parsed = JSON.parse(body);
    
        if (parsed.errors) {
          reject(
            new Error(
              `API request to fetch main theme ID failed: \n${JSON.stringify(
                 parsed.errors,
                 null,
                 '\t',
                )}`,
              ),
             );
            return;
          }
    
        if (!Array.isArray(parsed.themes)) {
          reject(
            new Error(
               `Shopify response for /admin/themes.json is not an array. ${JSON.stringify(
                 parsed,
                 null,
                 '\t',
               )}`,
             ),
           );
         return;
       }
    
    const mainTheme = parsed.themes.find((t) => t.role === 'main');
    
    if (!mainTheme) {
      reject(
        new Error(
          `No main theme in response. ${JSON.stringify(
             parsed.themes,
              null,
             '\t',
           )}`,
         ),
       );
     return;
   }
    
   resolve(mainTheme.id);
   });
  },
  );
 });
}
    
   module.exports = {
     sync(files = []) {
       if (!files.length) {
         return Promise.reject(new Error('No files to deploy.'));
       }
    
        filesToDeploy = [...new Set([...filesToDeploy, ...files])];
    
        return maybeDeploy();
      },
    
      replace() {
        return deploy('replace');
      },
    
    upload() {
      return deploy('upload');
    },
    
   fetchMainThemeId,
};
  

Source: ARM64 (Apple M1) support ยท Issue #1107 ยท Shopify/slate ยท GitHub

Optional: Fixing ssl issues (ie. styles/scripts not loading in localhost)

If the above changes have worked, you should find that Slate spins up a development server as expected. However, your styles and scripts may not be working. This is due to an issue with mkcert used to create a self-signed SSL certificate. If you are on a new Mac, you will need to install this.

  1. Install mkcert with homebrew: brew install mkcert
  2. Install a new CA into the key store: mkcert -install
  3. Run brew install nss
  4. If using FireFox, you may need to run arch -arm64 brew install nss and then re -run mkcert -install
  5. Copy and paste this bash function into your terminal (or into your .bashrc file if you want to have it available in the future):
    
    function ssl-check() {
      f=~/.localhost_ssl;
      ssl_crt=$f/server.crt
      ssl_key=$f/server.key
      b=$(tput bold)
      c=$(tput sgr0)
          
      # local_ip=$(ip route get 8.8.4.4 | head -1 | awk '{print $7}') # Linux Version
      local_ip=$(ipconfig getifaddr $(route get default | grep interface | awk '{print $2}')) # Mac Version
      # local_ip=999.999.999 # (uncomment for testing)
          
      domains=(
        "localhost"
        "$local_ip"
      )
          
      if [[ ! -f $ssl_crt ]]; then
        echo -e "\n๐Ÿ›‘  ${b}Couldn't find a Slate SSL certificate:${c}"
        make_key=true
      elif [[ ! $(openssl x509 -noout -text -in $ssl_crt | grep $local_ip) ]]; then
        echo -e "\n๐Ÿ›‘  ${b}Your IP Address has changed:${c}"
        make_key=true
      else
        echo -e "\nโœ…  ${b}Your IP address is still the same.${c}"
      fi
          
      if [[ $make_key == true ]]; then
        echo -e "Generating a new Slate SSL certificate...\n"
        count=$(( ${#domains[@]} - 1))
        mkcert ${domains[@]}
          
     # Create Slate's default certificate directory, if it doesn't exist
      test ! -d $f && mkdir $f
          
    # It appears mkcert bases its filenames off the number of domains passed after the first one.
    # This script predicts that filename, so it can copy it to Slate's default location.
      if [[ $count = 0 ]]; then
        mv ./localhost.pem $ssl_crt
        mv ./localhost-key.pem $ssl_key
      else
        mv ./localhost+$count.pem $ssl_crt
        mv ./localhost+$count-key.pem $ssl_key
      fi
      fi
    }  
    
  6. Run ssl-check in the terminal.
โš ๏ธ Note: macOS Monterey now uses ZSH as the default shell, so if you are used to using bash, you will need to type bash in the console to access the function in your .bashrc file, and then type exit to get back out.

Source: macOS - Error: Cannot install in Homebrew on ARM processor in Intel default prefix (/usr/local) - Stack Overflow

more posts