Features
Key features of Commoners.
Build Targets
Commoners relies on Vite to generate the essential frontend files for each build target.
Web
Web builds are the default build target. These builds are intended to be deployed to a web server, and are accessible from any device with a web browser.
PWA
Progressive Web Apps (PWAs) are web applications that can be installed on a device and accessed from the home screen. PWAs are supported on most modern browsers, and can be installed on both desktop and mobile devices—though they will have limited access to native features.
Commoners relies on vite-plugin-pwa to generate the necessary files for a PWA. To enable this feature, simply add the --target pwa
flag to your build command.
Desktop
Desktop builds are intended to be installed on a user's computer. These builds are accessible from the desktop, and have access to native features.
Commoners relies on Electron to generate the necessary files for a desktop application. To enable this feature, simply add the --target desktop
flag to your build command.
Mac
While code-signing, you may recieve a CSSMER_TP_CERT_REVOKED
error, which will cause a The application "X" can't be opened
error to appear when attempting to open the app.
To circumvent this, provide an appropriate certificate on your machine—or update the electron-builder
options in your configuration file to ignore code-signing:
export default {
// ...
electron: {
build: {
mac: {
identity: null
}
}
}
// ...
}
Workflow Configuration
To ensure that your Mac builds are code-signed, you'll need to create a Github Actions workflow to automate the build process. This will require you to define a set of secrets for @electron/notarize
, namely APPLE_ID
, APPLE_ID_PASSWORD
, and APPLE_TEAM_ID
.
APPLE_ID
- Your Apple ID email addressAPPLE_ID_PASSWORD
- An app-specific password generated from your Apple ID accountAPPLE_TEAM_ID
- Your Apple Developer Team ID (found in the Membership Details section of your account)
Additionally, you'll need to supply the p12-file-base64
and p12-password
values expected by the apple-actions/import-codesign-certs@v2
action. These are the base64-encoded contents of your .p12
file and the password used to encrypt it, respectively.
Note: To copy the contents of your
.p12
file, you can use the following command:base64 /path/to/certificate.p12 | pbcopy
Mobile
Mobile builds are intended to be installed on a user's mobile device. These builds are accessible from the home screen, and have access to native features.
Commoners relies on Capacitor to generate the necessary files for a mobile application. To enable this feature, simply add the --target mobile
flag to your build command.
One peculiar aspect of Capacitor is that mobile builds require Capacitor plugins to be explicitly listed in your package.json
file, even if installed in node_modules
.
iOS
If you are building for iOS, you will need Xcode installed on your Mac.
Ruby
- Install Homebrew
- Install
chruby
andruby-install
(brew install chruby ruby-install
) - Install and activate a different version of Ruby (
ruby-install ruby 3.3.0
andchruby 3.3.0
)
Tested Ruby Versions
- Ruby 3.3.0
Environment Configuration
An older version of CocoaPods may be required to build the project using Capacitor.
Try running the following command to install CocoaPods:
sudo gem install cocoapods:1.10.2
Publishing to TestFlight
Publishing your application requires Apple Developer Program membership.
App Store Connect Integration
To interact with the App Store Connect API, you'll need to create an API key in the App Store Connect dashboard. This key will be used to authenticate with the API and upload your build.
- Go to App Store Connect
- Click on "Users and Access", "Integrations", then "App Store Connect API"
- Click on the "+" button to create a new API key
- Provide a name, then select "Developer" access.
- You'll then need the
Issuer ID
andKey ID
from the key you just created- Finally, download the API key and store it in a secure location
Before we begin, you'll need to collect a range of different environment variables. These include:
APPLE_ID
- Your Apple ID email addressAPPLE_TEAM_ID
- Your Apple Developer Team ID (found in the Membership Details section of your account)APP_STORE_CONNECT_API_KEY_ISSUER_ID
- TheIssuer ID
from the App Store Connect API keyAPP_STORE_CONNECT_API_KEY_ID
- TheKey ID
from the App Store Connect API keyAPP_STORE_CONNECT_API_KEY_KEY
- The contents of the App Store Connect API key, copied usingopenssl pkcs8 -nocrypt -in path/to/key.p8 | pbcopy
APP_BUNDLE_IDENTIFIER
- The bundle identifier of your app (e.g.com.example.app
)APP_ID
- The App ID of your appTEMP_KEYCHAIN_USER
- The username for the temporary keychainTEMP_KEYCHAIN_PASSWORD
- The password for the temporary keychainCERTIFICATE_STORE_REPO
- The repository containing your certificates (from Fastlane Match)GIT_USERNAME
- Your GitHub usernameGIT_TOKEN
- A GitHub token with access to the repository containing your certificatesAPP_STORE_CONNECT_TEAM_ID
- Your App Store Connect Team IDMATCH_PASSWORD
- The password for your Fastlane Match
Manual Publishing
Coming soon...
Android
If you are building for Android, you will need to install the following dependencies:
Root Declaration
If you'd like to run a Commoners project but aren't at the base, you can run the CLI with an input path.
commoners /path/to/project
Services
Services are independent processes that the main application depends on. These may be local
or remote
based on the distributed application files.
Note: Commoners currently supports
.js
,.ts
,.py
, and.exe
services.
To declare a service, you simply add the source file path to the commoners.config.js
file:
export default {
services: {
test: './services/test/index.ts'
},
}
Before using a service, it's good practice to:
- Check for the existence of the service.
- Instantiate the appropriate URL from the
commoners
global object.
const { SERVICES } = commoners
if ('test' in SERVICES) {
const url = new URL(SERVICES.test.url)
// ....
}
If your service will be accessible from a particular URL, you'll want to make sure to instantiate your server using the HOST
and PORT
environment variables:
import http from 'node:http';
const host = process.env.HOST
const port = process.env.PORT
http.createServer((req, res) => { /* ... */ }).listen(port)
Using Services in Production
Service configurations may be different between development and production.
For instance, .py
services can be packaged into an .exe
file using pyinstaller
.
Python
The following configuration will allow you to include the --onedir
bundle from PyInstaller in your desktop
builds:
const pythonService = {
name: 'python-service',
src: 'src/main.py',
outDir: 'dist/pyinstaller',
}
export default {
// ...
src: pythonService.src,
build: `python -m PyInstaller --name ${pythonService.name} --onedir --clean ${pythonService.src} --distpath ${pythonService.outDir}`,
publish: {
src: pythonService.name, // The relative executable location in the base directory
base: `${pythonService.outDir}/${pythonService.name}`, // The base directory to copy
}
}
Note: You must refrain from using the same
--name
argument as your--distpath
final directory, as PyInstaller uses the[distpath]/../[name]
directory to store temporary files.
Plugins
Plugins are collections of JavaScript functions that run at different points during app initialization. These points include:
load
- After the DOM is loadeddesktop.preload
- Immediately on Electron main process instantiation (--desktop
builds only)desktop.load
- When Electron app is ready (--desktop
builds only)
Note: Official plugins can be found in the
@commoners
namespace on NPM, and are listed in the official plugins section.
To add a new plugin, simply provide a named Plugin
on the plugins
registry of your Configuration File:
export default {
plugins: {
selectiveBuild: {
isSupported: {
web: {
load: false
}
},
load: () => console.log(commoners.target + ' build (load)'),
desktop: {
load: () => console.log('desktop build (load)'),
preload: () => console.log('desktop build (load)')
}
}
}
}
To use a plugin, you should check for the existence of the plugin, which may have a return value stored in the PLUGINS
property.
However, some plugins are asynchronously loaded. You can use the READY
promise to ensure you're working with the resolved plugins:
const { READY } = commoners
READY.then(({ selectiveBuild }) => {
if (selectiveBuild) console.log('Loaded!')
})
Global variables will be loaded from your .env
file (if present). which you can use in desktop
load functions.