# Pitcher Watcher

Pitcher Watcher is a node.js-based CLI tool for both iOS and Windows environments, that automates the process of copying files from the project's source directory to the interactive/ui folder under /Pitcher Folders/. The watcher is configured to have a simple watch & copy workflow both for Vue applications where they have a build process and for legacy projects where there is not any build process involved.

# Installation

In order to install the @pitcher/watcher package, simply run:

npm install -D @pitcher/watcher

# Prerequisites

Before running the watcher, there are some important -but simple- steps that you need to perform.

# Before running on iOS

Make sure that your iOS simulator is up and running. If you haven't setup your simulator environment yet, take a look at the opening up simulator section. When you run a command and your target platform is ios, the watcher checks beforehand if there is a simulator running. If you are curious to find out more about this process check how it works section.

# Before running on Windows (Parallels)

  1. Make sure that Parallels is up and running.
  2. Make sure you are sharing your Parallels machine files with MacOS. This needs to be done only once.
How to share my Windows VM folders with MacOS

win-share-folder-1 win-share-folder-2

  1. If the watcher cannot mount your VM as a network drive automatically: Map Parallels device as network drive. This needs to be done each time you start Parallels.
How to map Parallels VM as Network drive

Open finder and click Network win-map-drive-1 Your parallels virtual drive should appear there. Click on it, then click further to map the drive win-map-drive-2

To map Parallels VM as a network drive permanently

If you would like to map the network drive automatically, follow the guide here (opens new window) and here (opens new window)

That's all! You are now ready to use the watcher.

# Usage

Pitcher Watcher supports 2 different commands:

  • pitcher-watcher-vue which is a specific command for Vue projects
  • pitcher-watcher which is compatible with any project

There are a couple of things that are in common between these commands. Both commands have two required arguments which are --fileID and --platform. With the given fileID, the watcher finds the destination folder depending on the platform.

If the platform argument value is ios:

  • The watcher tries to find out the currently running simulator devices. If there are multiple simulators running, the watcher asks you to select one of them in order to determine the device id, so that it has a more precise search path.
  • After finding the device id, the watcher then searches for the interactive folder with the given fileID.

If the platform argument value is win:

  • The watcher tries to find out network drives and asks you to select one. Make sure you have mapped Parallels machine as drive.
  • Next, the watcher asks you to select a user on Windows.
  • After finding the drive path and the user, the watcher searches for the interactive folder with the given fileID

After these common steps, the commands have a little bit different workflow from each other. Follow the details of each command below.

# pitcher-watcher-vue

This command is designed to be used with Vue projects.

When you run this command, the watcher:

  1. Finds the interactive/ui folder path with the given --fileID and --platform
  2. Cleans the contents of the directory found (unless you use --no-clean)
  3. Runs a specific vue-cli command e.g. vue-cli-service build --watch --mode development --dest '/SIMULATOR_PATH/Pitcher Folders/zip/123456/', which in its turn, builds your Vue project to the directory found in Step 1.

# Available arguments

argument required description
--fileID yes ID of the interactive/ui you are working on. This can be found on Pitcher Web Admin (opens new window).
--platform yes Target platform you want to copy your files to. Available values are: ios and win.
--vueArgs no Additional arguments to pass to the vue-cli-service command.
--watchMode no Watcher mode for HMR, ex: hot, live, manual (default: hot)
--wsPort no HMR server port, finds first available port starting from given port number (default: 8099)
--no-clean no By default, the watcher cleans the target directory when started. Use this option to disable this behavior.
--dest no This is not recommended to use unless you want to define the copy destination explicitly.

# Simple usage

# package.json
"scripts": {
  "ios:watch": "pitcher-watcher-vue --fileID=123456 --platform=ios",
  "win:watch": "pitcher-watcher-vue --fileID=123456 --platform=win"
}

# Usage with Live reloading

"scripts": {
  "ios:watch": "pitcher-watcher-vue --fileID=123456 --platform=ios --watchMode=live"
}

# Disable Hot/Live reloading

"scripts": {
  "ios:watch": "pitcher-watcher-vue --fileID=123456 --platform=ios --watchMode=manual"
}

# Passing arguments into vue-cli

"scripts": {
  "ios:watch": "pitcher-watcher-vue --fileID=123456 --platform=ios --vueArgs='--target lib --skip-plugins pwa,apollo'"
}

# pitcher-watcher

This command is designed to watch a folder and copy its contents to a specific location after a file change. The watcher here is based on chokidar (opens new window), and supports chokidar arguments as well. This command is useful when you have a project that is not build using a modern module bundler (e.g. a jQuery based app), and you only need to copy the contents of the folder to /Pitcher Folders/zip/FILE_ID after each change.

When you run this command, the watcher:

  1. Finds the interactive/ui folder path with the given --fileID and --platform
  2. Cleans the contents of the directory found (unless you use --no-clean)
  3. Starts watching the folder you added and after each change it copies the folder's contents to the directory found in Step 1.
argument required description
--fileID yes ID of the interactive/ui you are working on. This can be found on Pitcher Web Admin (opens new window).
--platform yes Target platform you want to copy your files to. Available values are: ios and win.
--paths no By default, the watcher watches the project's root. It is recommended to watch a specific folder e.g. 'src/'. Using --paths='folder_name/' differs than using --paths='folder_name' see example
--ignored no Set this option in order to ignore files/folders when watching & copying. By default the watcher ignores files/folders that start with . and the node_modules folder. You can ignore multiple paths e.g. 'utils/, jquery.js, *.json'
--includeDotFiles no false by default, the watcher ignores files/folders that starts with .
--includeNodeModules no false by default, the watcher ignores files/folders that match /node_modules/
--execAfter no This option bypasses copying after watching and runs the provided bash command.
--watchMode no Watcher mode for HMR, ex: hot, live, manual (default: hot)
--wsPort no HMR server port, finds first available port starting from given port number (default: 8099)
--no-clean no By default, the watcher cleans the target directory when started. Use this to disable this behavior.
--dest no This is not recommended to use unless you want to define the copy destination explicitly.

NOTE

As pitcher-watcher command is using chokidar (opens new window) in the background, you can use other arguments that chokidar supports. For further information, check chokidar documentation.

# Simple usage

# package.json

"scripts": {
  "ios:watch": "pitcher-watcher --fileID=123456 --platform=ios --paths='src/'",
  "win:watch": "pitcher-watcher --fileID=123456 --platform=win --paths='src/'"
}

# Copy folder content vs folder itself

# package.json

"scripts": {
  # --paths='src/' will copy the content of src folder
  "copy-folder-content": "pitcher-watcher --fileID=123456 --platform=ios --paths='src/'",
  # --paths='src' will copy the folder itself
  "copy-folder-itself": "pitcher-watcher --fileID=123456 --platform=ios --paths='src'"
}

# Ignoring files/folders

"scripts": {
  "ios:watch": "pitcher-watcher --fileID=123456 --platform=ios --paths='src/' --ignored='src/utils/'"
}

# Watching multiple folders

"scripts": {
  "ios:watch": "pitcher-watcher --fileID=123456 --platform=ios --paths='src/, dist/'"
}

NOTE

If you are watching multiple locations, the copy script will not consider the folder structure from paths. With that said, if you are watching ./src/ and utils/ folders at the same time, the copy script will copy the contents of src and the contents of the utils folder into the same directory and will not consider the folder structure.

# How it works

Unlike the standard watchers, Pitcher Watcher has a couple of extra steps that help the developers. An important feature Pitcher Watcher has, is finding the application directory on iOS or Windows environments. Before starting to use the Watcher, it is important to understand the process that takes place in the background.

For each command you use, it is important to provide --fileID and --platform arguments. Let's take a step by step look at what happens when you run pitcher-watcher or pitcher-watcher-vue commands.

Before the steps, you need to make sure you are running your iOS simulator for iOS and Parallels Virtual Machine for Windows projects. Otherwise Watcher will fail to run. When your Simulator or Parallels VM is up and running, the CLI will follow the steps below:

# 1. Finding the destination folder

The watcher first tries to find the destination folder with the given fileID under /Pitcher Folders/* depending on the --platform value. If the platform is ios, the watcher first checks if the simulator is running. If the simulator is running, the watcher will get the device ID from the active simulator to get a more precise location under /Library/Developer/CoreSimulator/Devices/${DEVICE_ID}/data/Containers/Data/Application.

  • If you are running multiple simulators, the watcher will ask you to select one.
  • If multiple folders that contains fileID found, the watcher will ask you to select a folder.

If the platform is windows, the watcher will first try to mount the Parallels VM as a network drive with help of osascript. Automounting Parallels VM depends highly on Virtual Machine name (default: Windows 10) for now. If your Parallels VM has another name than Windows 10, you will need to mount Parallels VM manually. If the watcher can not mount the drive at this step, it will then try to find out if there are any available network drives. If not, it will fail. It is very important to share your Windows folders with MacOS and then mount your Parallels VM as network drive to enable watcher for Windows. (instructions here). If everything goes fine, the watcher then will ask you to select a user folder to get a more precise location. If the watcher finds multiple folders that contains same fileID, the watcher will ask you to select a folder.

# 2. Cleaning the directory

After finding the target directory, the watcher cleans the contents of the target directory. Unlike vue-cli, it does not remove the folder itself but everything inside the directory. It does that in order to ensure that if the process fails midway, the directory will remain present (with the timestamp that Pitcher Impact looks for). You can simply disable this option if you pass the --no-clean argument to any of the commands.

# 3A. Building the project to the target directory (Vue projects only)

If you are using the pitcher-watcher-vue command, the copy process is different than the pitcher-watcher command. After finding out the target directory and cleaning it, watcher uses @vue/cli-service package internally to run vue-cli commands programmatically. Through @vue/cli-service, it runs build command with --watch and --dest parameter to build project to the destination folder under Pitcher Folders and watch for changes to re-build the project. As vue-cli solves the watch part and copy part with this command, it was not needed to include an extra watcher here.

# 3B. Watching the folder for changes

Unlike step 3A (Vue projects), if your project does not have a build step, the process is pretty much straight-forward. When you are using the pitcher-watcher command, after finding the target directory, the watcher will start watching the locations you have defined with the --paths argument. Whenever the watcher detects a change, it will simply copy the watched folder contents into the target directory. The copy action is performed through bash with the help of the rcopy command.

When a change is detected, the watcher will execute something like this:

rsync -r src/ destination/ --exclude ignored1/ --exclude ignored2/

# destination example (iOS): /Users/{USER_NAME}/Library/Developer/CoreSimulator/Devices/{DEVICE_ID}/data/Containers/Data/Application/{APPLICATION_ID}/Documents/Pitcher Folders/zip/{FILE_ID}

In the end, if you don't want to run the built-in copy script and you need to run a custom one, you can use the --execAfter argument to provide a bash script of your own. Usage of the execAfter argument will disable the built-in copy script and the watcher will execute the script that you passed to execAfter.

# 4A. Hot/Live reloading for Vue Projects

Hot/Live reloading feature in Vue projects works in a specific way for Pitcher needs. As the Watcher builds and copies the files into a folder under Pitcher Folders/, we actually don't run a development web-server in the background as you would do in a regular web project. Also, default HMR support provided by webpack/webpack-dev-server is not supported as we don't use serve command when we actually move our build files under Pitcher Folders. So then how does Hot/Live reloading works?

Depending on the --watchMode and --wsport arguments, Pitcher Watcher runs a simple WebSocket server integrated to the process. When you run the command, if --watchMode is not set to manual, Watcher first finds a free port (starting to search from 8099) and then saves the local network IP address. Your local IP address is needed at this point for Impact in Parallels VM Windows to be able to communicate with WebSocket server, as localhost in Parallels is not pointing to the host machine network.

After saving the local IP and port and before starting @vue/cli-service programmatically in step 3A , Watcher Webpack plugin is injected in to the @vue/cli-service build process. As @vue/cli-service uses webpack in the background, we needed a custom CLI plugin that will accomplish our custom Hot/Live reloading feature.

Webpack plugin in the process does multiple things:

  • Starts a WebSocket server on initialization -> to be able emit messages to the frontend on file changes/re-build.
  • Injects a code piece (hmr-helper.js) that is ES5 compatible in to consumer project bundle -> to be able to listen to the file changes/rebuild and Hot/Live reload the page.
  • Event hook that listens to file changes/build completion to emit messages through WebSocket server.

When everything is ready, build process starts together with the WebSocket server and on file changes/rebuild code piece inside hmr-helper.js file updates the page depending on the watchMode.

# 4B. Hot/Live reloading for Legacy Projects

Hot/Live reloading feature for legacy projects have a similar process until a certain point which is mentioned in step 4A. Similar to step 4A, watcher first finds the local IP address and a free port. After that, process differs a little bit than Vue projects. As legacy projects don't have a module bundler (webpack) or a build process, injecting Hot/Live reloading part is a bit more complicated than using Webpack.

After finding the local IP and a free port, before starting to watch folder changes, Watcher starts a WebSocket server (similar to step 4A) to be able to emit file changes to the Frontend. After that, watcher copies the watched files/folders from the project to the destination folder under Pitcher Folders. If --watchMode is hot or live, watcher then runs a function called useHtmlInject and it is where things starts to get a bit more complicated. To enable Hot/Live reloading, a helper JavaScript file needs to be injected to index.html file in the destination folder, this to be able to listen to the file changes on the watcher side. As a first step, Watcher first copies hmr-helper-legacy.js file to destination folder, then tries to find index.html under (Pitcher Folders/**/DEST_FOLDER/index.html). After finding the file, watcher parses the HTML and injects hmr-helper-legacy.js in to index.html in a <script> tag. If the watcher will not find index.html file in the destination folder, HMR will not be enabled.